Keen Slider React Component Always One Step Behind - reactjs

I installed the keen-slider library in my React project, and used the code from the App.js file in this example to set up a slider with page dots and navigation arrows. I am trying to modify that code, by passing in an array of React components, the size of which can be changed when the user selects or deselects options.
The problem is, the slider's dot count and arrow configuration always lags one step behind. If I move from 1 (default) to 2 pages selected, the rendered dot count stays at 1. When I increase to 3, it moves to 2. If I then decrease to 2, it goes to 3. It only catches up if I interact with the slider. In my App component's return, I place the slider as {keenSlider(outputComponentArray)}. To get outputComponentArray, I have some divs with onClick functions that toggle each page type's selected state. This array:
var selectedResultsConfig = [
['Proposal', outputs.proposal, resultSelectorProposal, setResultSelectorProposal],
['Map', outputs.map, resultSelectorMap, setResultSelectorMap],
['Front Page', outputs.frontPage, resultSelectorFrontPage, setResultSelectorFrontPage],
['Collage', outputs.collage, resultSelectorCollage, setResultSelectorCollage],
['Price Letter', outputs.priceLetter, resultSelectorPriceLetter, setResultSelectorPriceLetter],
['Line Items', outputs.lineItems, resultSelectorLineItems, setResultSelectorLineItems]
]
establishes what name, page component (in the 'outputs' object), and toggle state/setting function correspond to each other, then these buttons are rendered with .map on this array, like so:
{selectedResultsConfig.map((item, index) => {
return <>
{(index === 0) ? null : <> </>}
<div className={item[2] ? 'resultSelectorButton selectedButton' : 'resultSelectorButton'} onClick={() => { resultSelectToggle(item[0]) }}>
<Icon path={item[2] ? mdiCheckboxMarked : mdiCheckboxBlankOutline} size={1} color='#ecd670' />
<h2>{item[0]}</h2>
</div>
</>
})}
and their onClick function does the toggling like this:
function resultSelectToggle(button) {
if (screen === 'proposals') {
for (let i = 0; i < selectedResultsConfig.length; i++) {
if (button === selectedResultsConfig[i][0]) {
selectedResultsConfig[i][3](!selectedResultsConfig[i][2])
}
}
}
}
and then I have a useEffect hook that goes off after those toggles and sets up the final component array, which is fed to keen-slider:
//after the result selector button is toggled
useEffect(() => {
var tempComponentArray = [];
if (screen === 'proposals') {
for (let i = 0; i < selectedResultsConfig.length; i++) {
if (selectedResultsConfig[i][2]) {
tempComponentArray.push(selectedResultsConfig[i][1])
}
}
}
setOutputComponentArray(tempComponentArray);
}, [resultSelectorProposal, resultSelectorMap, resultSelectorFrontPage, resultSelectorCollage, resultSelectorPriceLetter, screen])
I'm not the most experienced with React, and I already know there are better ways of doing some of this, but it's not clear to me what is causing my issue. I was having a similar issue once that was fixed with useEffect, but I've already implemented that here. Any help would be appreciated, thanks.
I have made significant modifications to my original code to simplify it, and I originally passed keenSlider a functional component, but still the problem persists.

Related

How do i make a component like this in react?

What I needed was something like that shown in the picture. I need to show certain names and if the names list exceeds more than 2 rows I need to show +n others. If the user clicks +n others the list needs to be expanded to show all the others.
Is there any component available in react to get this result? I have seen it on a number of websites but don't know what they are called.
I could write the component myself but the difficult part would be how many names to show before i show the +n others. I can only show 2 rows initially and each name can be of variable length. So in one case, a single name may take up the entire 1st row and in others, i may be able to fit 3 names.
You have to store the state of the list see it's expanded or not.
Something like this should help.
import React, { useEffect, useState } from "react";
const myList = ({ list }) => {
const MAX_COUNT = 5;
const [isExpended, setIsExpended] = useState(false);
useEffect(() => {
if (list.length <= MAX_COUNT) {
setIsExpended(true);
}
}, [list]);
const expend = () => {
setIsExpended(true);
};
return (
<>
<div>
{list.map((item, i) =>
(isExpended === false && i < MAX_COUNT) || isExpended === true
? item.text
: null
)}
</div>
{isExpended === false ? (
<button onClick={expend}>+ {list.length - MAX_COUNT} others</button>
) : null}
</>
);
};
export default myList;
If you want to stick with 2 rows on any conditions there will be 2 approach to set the dynamic MAX_COUNT:
1: if you have a constant value for box-sizing and fonts and etc:
You can compute outerWidth of each elements (with box-sizing and elements length) and set a real MAX_COUNT based on that.
2: if you have responsive design:
you can render component with initial MAX_COUNT but hide contents with visibility: hidden and then computing outerWidth of each elements would be more realistic and much more easier.
in this scenario you have to fix the container height to prevent it from extending too much, just set a constant height to it, also you can change it after you get a real MAX_COUNT. now you can show contents with no worries.

Handle heavy resource/DOM on React Portal

I have created a react portal inside my application to handle the use of Modal. The portal target is outside of my React root div as sibling of my root element.
<html>
<body>
<div id="root">{{app.html}}</div>
<div id="modal-root">
<div class="modal" tabIndex="-1" id="modal-inner-root" role="dialog">
</div>
</div>
</body>
</html>
So my Portal contents renders outside of the react application and its working fine. Here is my react portal code
const PortalRawModal = (props) => {
const [ display, setDisplay ] = useState(document.getElementById("modal-inner-root").style.display)
const div = useRef(document.createElement('div'))
useEffect(()=> {
const modalInnerRoot = document.getElementById("modal-inner-root")
if(validate(props.showModalId)) {
if( props.showModalId == props.modalId && _.size(props.children) > 0 ) {
setDisplay("block");
if(_.size(modalInnerRoot.childNodes) > 0) {
modalInnerRoot.replaceChild(div.current,modalInnerRoot.childNodes[0]);
} else {
modalInnerRoot.appendChild(div.current);
}
div.current.className = props.modalInner;
document.getElementById("modal-root").className = props.modalClassName;
document.body.className = "modal-open";
} else {
document.getElementById("modal-root").className = props.modalClassName;
if(div.current.parentNode == modalInnerRoot) {
modalInnerRoot.removeChild(div.current);
div.current.className = "";
}
}
} else {
setDisplay("none");
document.getElementById("modal-root").className = "";
if(div.current.parentNode == modalInnerRoot) {
modalInnerRoot.removeChild(div.current).className = "";
}
document.body.className = "";
}
},[ props.showModalId ])
useEffect(()=> {
document.body.className = display == "none" ? "" : "modal-open";
document.getElementById("modal-inner-root").style.display = display;
return () => {
if(!validate(props.showModalId)) {
document.body.className = "";
document.getElementById("modal-inner-root").style.display = "none"
}
};
},[ display])
useEffect(()=> {
if(_.size(props.children) <= 0){
modalInnerRoot.removeChild(div.current)
document.body.className = "";
document.getElementById("modal-inner-root").style.display = "none";
}
return () => {
if(_.size(props.children) <= 0){
modalInnerRoot.removeChild(div.current)
document.body.className = "";
document.getElementById("modal-inner-root").style.display = "none";
}
}
},[props.children, props.showModalId])
return ReactDOM.createPortal(props.children ,div.current);
}
Whenever the children are passed and modal is mounted, The heavy DOM is painted with little delay. But the same markup takes time, or even crashes the browser tab. Where am I going wrong in handling the heavy DOM operations? Or is there any async way to handle the heavy DOM operations that wont effect the overall performance?
Couple of reasons can attribute for this :
The last effect will always run for every re-render as props.children is an object and hence even if same children was passed again, it'll be a new object.
Direct DOM manipulation is an anti-pattern, as React maintains several DOM references in memory for fast diffing, hence direct mutation may result in some perf hit.Try writing the same in a declarative fashion.
Extract out the portal content into another sub-component and avoid DOM manipulations wherever possible.
One place would be :
if (_.size(props.children) <= 0) {
modalInnerRoot.removeChild(div.current);
}
can be replaced within the render function like :
{React.Children.count(props.children) ? <div /> : null}
You just have to use the modal root as the createPortal host div (the second argument). React will just render there instead of in the regular element.
Then if you need to "manipulate" the HTML, just use plain React. It does not work any differently inside of portaled elements. All createPortal does is tell React to take this part of the tree and attach it under specified element.
Just include a permanent empty div with no style (no need to use any display rules), and it will just receive all HTML you want to render. Then you make the content inside the modal root fixed, but not the modal root itself.
Don't do this:
const div = useRef(document.createElement('div'))
// all kinds of manipulation of this div
return ReactDOM.createPortal(props.children ,div.current);
Do this instead:
const modalRoot = document.getElementById('modal-root');
function ModalWrapper({children}) {
return <div class="modal" id="modal-inner-root" role="dialog">
{ children }
</div>
}
function PortalModal({children}) {
return React.createPortal(
<ModalWrapper>{ children }</ModalWrapper>,
modalRoot
}
function App() {
const [hasConfirmed, setHasConfirmed] = useState(false);
return <div>
// ...
{ !hasConfirmed && <PortalModal>
<h1> Please confirm </h1>
<button onClick={() => setHasConfirmed(true)}>
Yes
</button>
</PortalModal> }
</div>
}
You can perfectly manage the state of the component in the modal, and whether it should show the modal at all. You won't need to do any manual DOM manipulation anymore.
Once you stop rendering the portal, React removes it for you.
Why does the question's code have performance issues?
It does many DOM operations which, among other things, will result in style recalculations. You shouldn't have to do manual DOM operations at all, it's exactly what React is built to handle for you. And it's reasonably efficient at it.
Since you're doing it in useEffect, React has already triggered style recalculations and the result of that was painted to the screen. This is now immediately invalidated, and the browser needs to recalculate some amount of elements.
Which amount of elements?... All of them, because a style recalculation on the body is triggered by changing its classname.
document.body.className = "modal-open";
If you have a heavy DOM, a full style recalculation can quickly take long and cause noticeable stutter. You can avoid this by not touching the body and just adding an overlay div you can show and hide.
Can it cause a tab to crash though? Maybe in extreme cases, but probably not.
It's more likely that you're calling this component in a way that creates an infinite loop. Or you may be ending up doing a ridiculous amount of DOM operations. It's impossible to tell without the full code used when the performance issues were noted.

What's wrong with enabling a button when any of checkboxes is selected with my implementation with react hooks? [duplicate]

This question already has answers here:
How to implement multiple checkbox using react hook
(5 answers)
Closed 1 year ago.
I have a bug in enabling a button, when any of the checkboxes is selected. Currently it doesnt work on the first click, but only on the second one. Unselecting a checkbox works on the first click also. I think it has something to do with states, but I don't really understand what is causing the problem.
I tried commenting out this line:
checked.length > 0 ? setTaskBtnsEnabled(true) : setTaskBtnsEnabled(false);
and it removes the issue so I'm sure it has something to do with the useState. However that line is vital for me so I should come up with a fix for this. I also tried to start with opposite boolean values and the issue remains.
Upper component:
const checkedBoxes = () => {
var checkedOnes = [];
for (
var i = 0;
i < document.getElementsByClassName('count-checkboxes').length;
i++
) {
if (document.getElementsByClassName('count-checkboxes')[i].checked) {
checkedOnes.push(
document.getElementsByClassName('count-checkboxes')[i].parentNode
.id
);
}
}
return checkedOnes;
};
const [taskBtnsEnabled, setTaskBtnsEnabled] = useState(false);
const handleBtnsEnabling = event => {
var checked = checkedBoxes();
checked.length > 0 ? setTaskBtnsEnabled(true) : setTaskBtnsEnabled(false);
};
Component inside the previous one:
<Button
disabled={!taskBtnsEnabled}
id="nappi"
>
OK
</Button>
The issue is that first click doesn't work and it doesn't check the checkbox. No error messages coming to console.
Try creating one function that each checkbox click will call. This function will increase or decrease the value of a state variable. Something like
const [numberOfChecked, setNumberOfChecked] = useState(0)
Then add a useEffect that will watch for changes to numberOfChecked. Inside that hook you could then do your
useEffect(() => {
setTaskBtnsEnabled(numberOfChecked > 0);
}, [numberOfChecked]);

Why does Object.keys(this.refs) not return all keys?

Hi,
so I've redacted some sensitive information from the screen shot, but you can see enough to see my problem.
Now, I'm trying to build the UI for a site that gets data from a weather station.
I'm trying to use react-google-maps' InfoBox, which disables mouse events by default.
It seems that to enable mouse events, you must wait until the DOM is loaded, and then add the event handlers.
react-google-maps' InfoBox fires an onDomReady event (perhaps even upon adding more divs) but seems to never fire an onContentChanged event (I've looked in the node_modules code).
The content I'm putting in the InfoBox is basically a div with a string ref for each type of weather data. Sometimes there comes along a new type of weather data so I want to put that in also, and have the ref be available / usable.
However, immediately after the new divs have been added (and the DOM has been updated to show them), when I try to console log the DOM nodes (the refs refer to the nodes because they are divs and not a custom built component) the latest added ones are undefined.
They do become a div (not undefined) a few renders later.
I've contemplated that this may be because
1) the DOM is not being updated before I'm trying to access the refs, but indeed the UI shows the new divs,
2) string refs are deprecated (React 16.5),
but they work for the divs in comonentDidMount and eventually for new divs in componentDidUpdate,
3) executing the code within the return value of render may be run asynchronously with componentDidMount, but I also tried setTimeout with 3000 ms to the same effect,
4) of something to do with enumerable properties, but getOwnProperties behaves the same way.
In the end I decided I'll console log this.refs and Object.keys(this.refs) within the same few lines of code (shown in the screen shot), and you can see that within one console log statement (where Object.keys was used in the previous line) that while this.refs is an object with 8 keys, the two most recently added refs don't appear in Object.keys(this.refs).
This is probably a super complex interaction between react-google-maps' InfoBox, React's refs, and JavaScript's Object.keys, but it seems like it should be simple and confuses me to a loss.
Can anyone shed some light on why this might be happening??
The code looks something alike:
class SensorInfoWindow extends React.Component {
handleIconClick = () => {
// do stuff here
}
componentDidMount() {
this.addClickHandlers();
}
componentDidUpdate() {
this.addClickHandlers();
}
addClickHandlers = () => {
const keys = Object.keys(this.refs);
for(let i=0; i<keys.length; i++) {
const key = keys[i];
let element = this.refs[key];
if (element !== undefined)
element.addEventListener('click', this.handleIconClick);
}
}
render() {
const { thissensor, allsensors } = this.props;
let divsToAddHandlersTo = [];
const sensorkeys = Object.keys(allsensors);
for (let i=0; i<sensorkeys.length; i++) {
divsToAddHandlersTo.push(
<div
ref={'stringref' + i}
/>
{/* children here, using InfoBox */}
</div>
);
}
return (
<div>
{divsToAddHandlersTo}
</div>
);
}
}
This is, in essence, the component.

React: img onLoad and Chicken/Egg problem I cannot escape

I have a React control that renders a bunch of images. My goal is to avoid the flickering that is caused by an unknown time it takes React to load the images (yes, I know about inline image loading, let's pretend it doesn't exist for a moment)
I have an initialized array in my class:
this.loadedImages = [];
For this purpose I use onLoad in this manner:
render () {
let items = this.props.images.map((value, index) => {
let style = {};
if (this.isImageLoaded(index))
style = value.style;
else
style = {visibility: 'hidden'};
return <img
key={ index }
onClick={ this.onClick }
onLoad={ this.onLoad(index) }
style={ style }
src={ value.image }
alt={ value.alt}/>
});
return (
<div>
{items}
</div>
);
}
}
my onLoad and isImageLoaded look like this:
onLoad = (index) => {
if (!this.isImageLoaded(index)) {
this.loadedImages.push(index);
}
};
isImageLoaded = (index) => {
let isloaded = this.loadedImages.includes(index);
if (isloaded)
console.log(index + " is loaded!");
else
console.log(index + " is NOT loaded ");
return isloaded;
};
The issue is that once my page loads, the images switch from a "not loaded" into a "loaded" mode -- BUT there is only ONE RENDER that occurs before the images are loaded, thus the {visibility: 'hidden'} style remains permanent.
So my page loads without images. Now, if I click my component even once, the images will appear correctly because the component is forced to re-render (since now the images are loaded). BUT there is no option for me to force such a re-draw programmatically from the onLoad function as I'm getting a warning I should not be doing that from render...
My question is: how can I break the chicken/egg problems here and re-render my component once any image completes its loading.
I suggest combining your loadedImages data with the your other image state (as a boolean flag on each) and updating it using setState every time one loads (your headaches are due to this separation and the fact that you are having to manually keep them synchronised).
Then map over the single array of images (including loading state), using something like the src for the key.

Resources