Using SwipeableViews with updateHeight() - reactjs

A new update to SwipeableViews allows for each Tab in the MaterialUI Tabs Component to have their own height. Let me back up a little. Before, MaterialUI Tabs component will show all tabs in the height of the longest tab. This created unnecessary scrolling on tabs that didn't need it. SwipeableViews recently fixed this by adding this to their component
<SwipeableViews
action={actions => {this.swipeableActions = actions;}}>
<div>{'slide n°1'}</div>
<div>{'slide n°2'}</div>
<div>{'slide n°3'}</div>
</SwipeableViews>
and
componentDidUpdate() {
this.swipeableActions.updateHeight();
}
That fixed the height issue across tabs on their loaded state. But when items are hidden and shown with a click event, the height persists and shown items do not show (being cut off from view)
See image (imgur failed to load the image) see the image link instead
image

You need to add:
animateHeight
to the SwipeableViews component.
You may also have to pass a function down to child components in order to update the height when a child modifies the view.
UpdateSwipeableViewHeight = () => {
if (this.swipeableActions) this.swipeableActions.updateHeight()
};
<SwipeableViews
id="searchTabsSwipeableView"
axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={activeTabIndex}
onChangeIndex={index => {
this.handleChange(null, index);
this.UpdateSwipeableViewHeight();
}
}
action={actions => (this.swipeableActions = actions)}
animateHeight
>
<div className={classes.swipableViewsComponent}>
<ChildComponent updateHeight={this.UpdateSwipeableViewHeight} />
</div>

Related

Keen Slider React Component Always One Step Behind

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.

Recording user interactions in React Native / Snap Carousel - firstItem

I have been having this issue for a couple of days and cannot seem to find a solution.
I would like to record the user's interaction on a database. I am displaying data using a React Native Snap Carousel, and need to record if the item was viewed by the user. To do this, and as described in other stack overflow questions, I am using "onSnapToItem". onSnapToItem is triggered everytime I change from one slide to another. Then, if the user views the slide for more than 2 seconds, I count that as an interaction.
The problem is that onSnapToItem is not triggered on the firstItem (which makes sense, because I am not changing slide). Can anybody think of a solution?
<Carousel
vertical
layout={"default"}
ref={_carousel}
data={promos}
renderItem={_renderItem}
autoplay={true}
autoplayInterval={4000}
onSnapToItem = { (index) => {
clearTimeout(writeDelay)
writeDelay=setTimeout (()=>{
console.log(promos[index].id)
},2000)
}}
onEndReached={_retrieveMore}
/>
Programmatically snap to item on screen focus.
componentDidMount() {
setTimeout(() => this._carousel.snapToItem(0), 1000);
// if above code doesn't work then you can try is for first item
writeDelay=setTimeout (()=>{
console.log(promos[0].id); // index here is 0 for first item
},1000)
}
...
render() {
<Carousel
...
ref={c => { this._carousel = c }}
/>
}`
you can use this library #svanboxel/visibility-sensor-react-native

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.

Component is not re rendering after state change in react

While working on my project I faced this issue and tried for two days to solve it. Finally I decided to post it here.
I am rendering array by calling child component as follows:-
Parent component
{this.state.list.map((item,index)=>{
return <RenderList
insert={this.insert.bind(this,index)}
index={index}
length={this.state.list.length}
EnterPressed={this.childEnterPressed.bind(this,index)}
list={item}
onchange={this.childChange.bind(this)}
editable={this.state.editable[index]}
key={index}
onclick={this.tagClicked.bind(this,index)} />
})}
and
Child component
<Draggable grid={[37, 37]}
onStop={this.handleDrop.bind(this)}
axis="y"
onDrag={this.handleDrag.bind(this)}
bounds={{top: -100, left: 0, right: 0, bottom: 100}}
>
<div onClick={this.tagClicked.bind(this)}>
{ this.props.editable ? (<Input
type="text"
label="Edit Item"
id="listItem"
name="listItem"
onKeyPress={this.handleKeyPress.bind(this)}
defaultValue={this.props.list}
onChange={this.onChange.bind(this)} />) :
(<Chip>{this.props.list}</Chip>) }
</div>
</Draggable>
I am using react-draggble to make each individual item of my list draggble. When ot is dragged in handleDrag function I am just updating my state of distance by which component is draged and in handleDrop function I am calling insert function which delete the dragged item from its current position and insert at position where it is dropped.
insert=function(index,y,e){
if(y!==0){
var list=this.state.list;
var editable = Array(this.state.editable.length).fill(false);
if(y/37>0){
var temp=list[index];
var i=0;
for(i=index;i<index+y/37;i++){
list[i]=this.state.list[i+1];
}
list[(y/37)+index]=temp;
this.setState({
...this.state,
list:list,
editable:editable
})
}
}
State is updating correctly. I have tested it but component is not re rendering. what surprised me is that if I just display the list in the same component then it re render correctly.any help is appreciated!
React cannot keep track of the items, as your are using the index as key (which is discouraged). Try to create a unique key based on something like a generated uuid which sticks to the item.
When dragging an item, the position (index) in the array changes. This prevents React from syncing the correct elements. Prop/state changes may not propagate as wanted.

React - scrolling to the bottom of a flex container without scrolling the whole page

I'm using react and trying to build a facebook like chat - where the right column scrolls down and has a list of threads, and the middle column had the thread content and scrolls bottom up.
I have it all setup using flex, but am stuck on getting the middle column to scroll to the bottom by default (since I want to see the latest chat message). Here is the snippet for the actual container (I'm using React bootstrap):
<Row >
<Col ref={ (node) => { this.cont = node }}>
{this._buildThread()};
</Col>
</Row>
and here is the snippet for my ComponentDidMount:
componentDidMount() {
MessageStore.addChangeListener(this._onChange);
MessageService.getThread(this.props.id, 1000, 1, false);
this.cont.scrollTop = this.cont.scrollHeight;
}
Where cont is the ref for the container div that holds my messages. Yet nothing happens - it doesn't scroll, and if I look at node.scrollTop after setting it, it remains at 0 - seems immutable.
Any help would be much appreciated. Thanks!
Not sure about the details of your CSS, but I just had a similar-looking issue. The solution in my case was to make sure that the element itself would scroll and not its container:
.containerIWantToScroll {
overflow-y: auto;
}
If the element's container expands to fit the element, then that element will not scroll, thus its scrollTop value is always zero.
When are you triggering the code you included? To scroll to the bottom on render, I would write something like the following, calling triggerScroll in componentDidMount:
class App extends React.Component {
componentDidMount () {
this.triggerScroll();
}
triggerScroll () {
this.cont.scrollTop = this.cont.scrollHeight;
}
render () {
return (
<div>
<div className='containerIWantToScroll' ref={ (node) => { this.cont = node } }>
Content
</div>
</div>
)
}
}

Resources