Update Redux prop/state following div onclick - javascript

I have a table - let's call it table 1. When clicking on a row in table 1 another table is being displayed, let's call this one table 2. Table 2 displays data relevant to the clicked row in table 1. Sometimes a vertical scroll needs to be displayed in table 2 and sometimes not -depends on the number of rows.Need to solve: there is an unwanted transition of the border when the scroll is not being displayed:
. The idea for the solution: "change margin-right" according to conditions which show whether the scroll exits or not.Save the result of this condition into Redux prop:
element.scrollHeight > element.clientHeight || element.scrollWidth >
element.clientWidth
The problem: Trying to update the display/non-display of the scroll into redux prop from different React events such as componentDidMount, componentWillReceiveProps,CopmponentDidUpdate (set state causes infinte loop here) and from the click event.Tried to use forceUpdate() after setting props into Redux as well.
When console.log into the console in chrome (F12), the only result which is correlated correctly to the display/non display of the scrollbar is coming from within the componentDidUpdate and it doesn't reflect in the redux prop (isoverflown function returns true, redux this.props.scrollStatus and this.state.scrollStatus are false). Also don't like the usage of document.getElementById for the div which contains the rows, because it breaks the manipulation of the dom from within the props and state,but didn't find a different solution for now.
The F12 console when display the scroll bar:
The F12 console when no scroll bar is displayed:
.
The rest of the code:
1) action:
export function setScrollStatus(scrollStatus) {
return {
type: 'SET_SCROLL_STATUS',
scrollStatus: scrollStatus
};
}
2) reducer:
export function scrollStatus(state = [], action) {
switch (action.type) {
case 'SET_SCROLL_STATUS':
return action.scrollStatus;
default:
return state;
}
}
3)Page.js (please click on the picture to see the code)
import {setScrollStatus} from '../actions/relevantfilename';
function isOverflown(element) {
return element.scrollHeight > element.clientHeight ||element.scrollWidth > element.clientWidth;
}
class SportPage extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
this.state = {
scrolled:false,
scrollStatus:false};
componentDidUpdate() {
console.log( "1 isoverflown bfr redux-this.props.setScrollStatus inside componentDidUpdate",isOverflown(document.getElementById('content')));
//redux props
this.props.setScrollStatus( isOverflown(document.getElementById('content')));
console.log( "2 isoverflown aftr redux-this.props.setScrollStatus inside componentDidUpdate",isOverflown(document.getElementById('content')));
//redux props
this.props.scrollStatus ? console.log (" 3 this.props.scrollStatus true inside componentDidUpdate") : console.log("this.props.scrollStatus false inside componentDidUpdate");
console.log ("4 state scrollstatus inside componentDidUpdate" , this.state.scrollStatus)
}
componentDidMount(){
console.log( "3 isoverflown bfr set",isOverflown(document.getElementById('content')));
this.props.setScrollStatus("set inside didMount", isOverflown(document.getElementById('content')));
console.log( "4 isoverflown aftr set didMount",isOverflown(document.getElementById('content')));
this.props.scrollStatus ? console.log ("scrollStatus true") : console.log("scrollStatus false");
console.log ("state scrollstatus inside didMount" , this.state.scrollStatus)
}
render() {
<div style={{overflowY:'scroll',overflowX:'hidden',height:'50vh',border:'none'}}>
{
this.props.rowData.map((row,index )=>
<div style={{ display: 'flex',flexWrap: 'wrap', border:'1px solid black'}}
onClick={ e => { this.setState({ selected: index, detailsDivVisible: true,scrolled:isOverflown(document.getElementById('content')),
scrollStatus:isOverflown(document.getElementById('content')) },
this.props.setScrollStatus( isOverflown(document.getElementById('content'))),this.forceUpdate(),console.log ("onclick this.state.scrollStatus", this.state.scrollStatus),
console.log ("onclick pure funtion", isOverflown(document.getElementById('content'))));
const mapDispatchToProps = (dispatch) => {
return {
setScrollStatus: function (scrollStaus) {dispatch (setScrollStatus(scrollStaus))},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Page);

Thank you for your reply. However,solved it in different way which does not involve the life cycle/events:
1) Calculate the height of the scroll by- multiple the height of single row by number of items to be displayed (arr.length, the arr comes from JSON)
2) setting the max height of the scroll to a needed value
3) setting the max height of the content to be the calculated height:
The result is a scroll that displays all the time with the correct height. This solved the indentation problem.
<div style={{overflowY:'auto', marginRight: '18px',zIndex:'1000',borderBottom:'1px solid black',borderRight:'1px solid black', height: this.props.rowData[this.state.selected].rowItemsList.length * singleRowHeight + 'px', maxHeight:'100px' }}>
<div style={{ width:'inherit', maxHeight:this.props.this.props.rowData[this.state.selected]‌​.rowItemsList.length * singleRowHeight + 'px' }}>

Lets simplify this. All you need is to dispatch reducer each time some one clicks inside a div.Please find the code snippet useful please go through the comments.
//import store from "./store/directory" - update this to ur store
let DOMObject = document.getElementById("id1"); //dom reference i did it based on ID its up to u to refer how u like it
//call back happens each time onclick event is triggered
DOMObject.onclick = ()=> {
/* store.dispatch(
{
type:"reducer to invoke",
data:"the data to update on click"
}
);
*/
//uncomment above and update to your requirement
console.log("clicked - Please update the dispatch event to you requirement");
}
#id1 {
padding :100px 150px 100px 80px;
background-color: lightblue;
}
<div id="id1">
DIV AREA - clcik
</div>

Related

My React function gets called twice, with the second call setting values incorrectly

i have a function that calculates pages and page buttons based on an api. Inside the buttons get rendered and they have an onClick function. When i click the button, this is supposed to happen:
sets the current page number and writes it into state
calls the api which gets text elements to display according to current page
evaluates page buttons and numbers based on api and marks the current page with a css class
event handler:
handleClick(event) {
let currentPage = Number(event.target.id)
localStorage.setItem("currentPage", currentPage)
this.setState ({
currentPage: currentPage
})
this.fetchApi()
}
then i'm returning the component that deals with pages:
return(
<div>
<Paging
data = {this}
currentPage = {this.state.currentPage}
state = {this.state}
lastPage = {this.state.lastPage}
handleClick = {this.handleClick}
/>
</div>
)
and the component looks like this:
function Paging(props) {
const apiPaging = props.state.apiPaging
const apiPagingSliced = apiPaging.slice(1, -1)
const renderPageNumbers = apiPagingSliced.map((links, index) => {
return <button key={index} id={links.label}
onClick={(index)=>props.handleClick(index)}
className={(links.active ? "mark-page" : "")}
>{links.label} {console.log(links.label) }
</button>
})
return (
<div id = "page-nums">
{renderPageNumbers}
</div>
)
So what happens is that Paging() function gets called twice. There is a handy value inside the api called "active" (links.active) which is a boolean, and if set to true, means that the page is the current page. i then add a class "mark-page" on to highlight that i'm currently on that page. If i {console.log(links.label)} i see that it's invoked twice, first being the correct values and second being the previously clicked values. So it works correctly only if i reload the page again.
i.e if i click page 2,it stays on page 1 and marks page 1. if i then click page 3, it marks page 2. and (afaik) Paging() gets only invoked once, at the end of my only class (Body).
I've been at it yesterday and today and have no idea anymore.
change your handleClick function to this.
handleClick(event) {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
if (event.target.id >= 1) {
let currentPage = Number(event.target.id);
localStorage.setItem('currentPage', currentPage);
this.setState({
currentPage: JSON.parse(localStorage.getItem('currentPage')),
},()=>{
this.fetchApi();
});
}
}
in your fetchApi function you reference currentPage as below.
const apiQuery = JSON.parse(this.state.currentPage);
But it hasn't updated yet.
see https://reactjs.org/docs/react-component.html#setstate

React: componentWillRecieveProps is one step behind the parent state at all times

So the user makes changes to the state through an input element with this function:
handleCardChange = (event, style) => {
let updated = event.target.value;
this.setState(previousState => ({
styling: {
...previousState.styling,
styling: {
...previousState.styling.styling,
card: {
...previousState.styling.styling.card,
height: updated
}
}
}
}))
}
This updates the state fine, for example if the user puts "600px" in the field then the state will be 600px. Since the state has changed, I want to trigger a re-render of my child component since it uses its props.
I used a componentWillReceiveProps() for this like so:
componentWillReceiveProps(nextProps) {
console.log("componentWillRecieveProps: ", this.props.styling.styling.card.height);
this.state = {
styling: this.props.styling.styling
}
}
The problem I'm running into is that the child is behind the parent by one character at all times.
So if parents height is 600px, the childs value is 600p and I need to add another character in the input for the child to update.
Is there any way to fix this and make sure the child component will keep up to date with the parents current state?

React state is out of sync between render method and what is actually displayed on page

I need to access DOM elements outside of my React app, which may load slower than my app. Then I need to update my state to render a few different things. To do that I am polling for the DOM elements with a recursive function that gets kicked off from componentDidMount(). I'm seeing a weird issue where once the element is found and I've updated the state, things get out of sync. In the render function, my console.log() shows the updated state value, in React Developer Tools I see the updated state value, but on the actual rendered page I see still see the old state value.
Code:
// initially doesn't exist. Added to the DOM after 3 seconds
let slowElement = document.querySelector('.external-dom-element')
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
showFoundSlowElementMessage: false,
slowElementCheckMaxAttempts: 5,
slowElementCheckCount: 0,
}
this.checkForSlowElement = this.checkForSlowElement.bind(this)
}
componentDidMount () {
this.checkForSlowElement()
}
checkForSlowElement () {
slowElement = document.querySelector('.external-dom-element')
if (slowElement !== null) {
console.log('found') // element found, show message
this.setState({
showFoundSlowElementMessage: true
})
} else {
console.log('not found') // element not found, increment count and check again after delay
this.setState({
slowElementCheckCount: this.state.slowElementCheckCount + 1
}, () => {
if (this.state.slowElementCheckCount < this.state.slowElementCheckMaxAttempts) {
window.setTimeout(this.checkForSlowElement, 1000)
}
})
}
}
render() {
const foundSlowElement = this.state.showFoundSlowElementMessage
? <p>Found slow element</p>
: <p>No sign of slow element, checked {this.state.slowElementCheckCount} times</p>
// null until it is added to the page
console.log(foundSlowElement)
return (
<div>
<h1>Hello</h1>
{foundSlowElement}
</div>
);
}
}
}
ReactDOM.render(<App />, document.getElementById('react-target'));
// Simulate slow element by adding it to the DOM after 3 seconds
window.setTimeout(() => {
const root = document.getElementById('root');
const newElement = '<div class="external-dom-element">slow element</div>';
root.innerHTML += newElement;
}, 3000)
Working example on codepen
I figured this out myself. It has nothing to do with my component, it's the demo itself that is breaking it. When I simulate the slow element by appending the root element's inner html:
root.innerHTML += newElement;
It re-parses the entire element and React loses all of the event handlers, etc. that it had previously set up.
This thread helped me out

How to detect hidden component in React

In brief,
I have a infinite scroll list who render for each Item 5 PureComponent.
My idea is to somehow, only render the 5 PureComponent if the Item is visible.
The question is,
How to detect if the Item component is visible for the user or not?
Easiest solution:
add scrollPosition and containerSize to this.state
create ref to container in render()
<div ref={cont => { this.scrollContainer = cont; }} />
in componentDidMount() subscribe to scroll event
this.scrollContainer.addEventListener('scroll', this.handleScroll)
in componentWillUnmount() unsubscribe
this.scrollContainer.removeEventListener('scroll', this.handleScroll)
your handleScroll should look sth like
handleScroll (e) {
const { target: { scrollTop, clientHeight } } = e;
this.setState(state => ({...state, scrollPosition: scrollTop, containerSize: clientHeight}))
}
and then in your render function just check which element should be displayed and render correct ones numOfElementsToRender = state.containerSize / elementSize and firstElementIndex = state.scrollPosition / elementSize - 1
when you have all this just render your list of elements and apply filter base on element's index or however you want to sort them
Ofc you need to handle all edge cases and add bufor for smooth scrolling (20% of height should be fine)
You can use the IntersectionObserver API with a polyfill (it's chrome 61+) . It's a more performant way (in new browsers) to look for intersections, and in other cases, it falls back to piro's answer. They also let you specify a threshold at which the intersection becomes true. Check this out:
https://github.com/researchgate/react-intersection-observer
import React from 'react';
import 'intersection-observer'; // optional polyfill
import Observer from '#researchgate/react-intersection-observer';
class ExampleComponent extends React.Component {
handleIntersection(event) {
console.log(event.isIntersecting); // true if it gets cut off
}
render() {
const options = {
onChange: this.handleIntersection,
root: "#scrolling-container",
rootMargin: "0% 0% -25%"
};
return (
<div id="scrolling-container" style={{ overflow: 'scroll', height: 100 }}>
<Observer {...options}>
<div>
I am the target element
</div>
</Observer>
</div>
);
}
}

Get React.refs DOM node width after render and trigger a re-render only if width has value has changed

I'm attempting to get the width of a ref DOM element and set state to then use within the Component render. The problem comes because this width changes on user input and when I try setState within componentDidUpdate it starts an infinite loop and my browsers bombs.
I created a fiddle here http://jsbin.com/dizomohaso/1/edit?js,output (open the console for some information)
My thinking was;
Component Mounts, setState: refs.element.clientWidth
User inputs data, triggers render
shouldComponentUpdate returns true only if new.state is not equal to old.state. My problem is, I'm not sure where makes sense to update this state?
Any help will be much appreciated, thanks for reading!
Brad.
var component = React.createClass({
componentDidMount: function() {
//Get initial width. Obviously, this will trigger a render,
//but nothing will change, look wise.
//But, if this is against personal taste then store this property
//in a different way
//But it'll complicate your determineWidth logic a bit.
this.setState({
elWidth: ReactDOM.findDOMNode(this.refs.the_input).getBoundingClientRect().width
})
},
determineWidth: function() {
var elWidth = ReactDOM.findDOMNode(this.refs.the_input).getBoundingClientRect().width
if (this.state.elWidth && this.state.elWidth !== elWidth) {
this.setState({
elWidth: elWidth
})
}
},
render: function() {
var styleProp = {}
if (this.state.elWidth) {
styleProp.style = { width: this.state.elWidth };
}
return (
<input ref="the_input" onChange={this.determineWidth} {...styleProp} />
)
}
})
I like to use .getBoundingClientRect().width because depending on your browser, the element might have a fractional width, and that width will return without any rounding.

Categories