I am trying to get scrollPosition using getSnapshotBeforeUpdate lifecycle method. I went through below React official site to understand about new life cycle method getSnapshotBeforeUpdate
But I am not able to find any scroll position. I console.log all the output, but didn't find anything in console.
This is my code file:
import React from 'react';
class ScrollingList extends React.Component {
constructor(props){
super(props);
this.listRef = React.createRef();
}
getStaticContent() {
const result = [];
for( let i= 1; i <=100; i++){
result.push(<li key={i}>{i} > This is paragraph line-{i}</li>);
}
return result;
}
getSnapshotBeforeUpdate(preProps, preState) {
// const list = this.listRef.current;
// console.log(list);
if(preProps.list.length > this.props.list.length) {
const list = this.listRef.current;
console.log(list);
return list.ScrollingHeight - list.ScrollTop;
}
return null;
}
componentDidUpdate(preProps, preState, snapshot) {
console.log(snapshot);
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return(
<div ref={this.listRef}>
{/* <div className="content-wrap">
<ul>
{this.getStaticContent()}
</ul>
</div> */}
</div>
);
}
}
export default ScrollingList;
Error: TypeError: Cannot read property 'length' of undefined
How I can get scroll position using this life cyle method?
You have use OffsetHeight and scrollTop , please try this modified code.
getSnapshotBeforeUpdate(preProps, preState) {
if(preProps.list.length > this.props.list.length) {
const list = this.listRef.current;
console.log(list);
const { current } = this.listRef;
const isScrolledToBottom =
current.scrollTop + current.offsetHeight >= current.scrollHeight;
return { isScrolledToBottom };
}
return null;
}
Related
I try to append a component dynamically, here it's Boards that i try to add to MainComponent.
The function addChild work and the list boards is filling up when the loop for is running, but nothing else happen, nothing is displayed on the screen
const MainComponent = props => (
<div className={props.className}>
<p>
<a href="#" onClick={props.addChild}>Add Another Child Component</a>
</p>
{props.boards}
</div>
);
class MainPage extends React.Component {
state = {
numChildren: 0
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
});
}
render () {
const boards = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
var _id = "board-" + i;
console.log
boards.push(<Board id={_id} className="board" />);
};
return (
<div className="test">
<Title/>
<MainComponent addChild={this.onAddChild} className="flexbox">
{boards}
</MainComponent>
</div>
);
}
}
export default MainPage```
Try to add key for each ChildComponent
const boards = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
var _id = "board-" + i;
console.log
boards.push(<Board id={_id} key={_id} className="board" />);
};
because react depends on the key for rendering the component that is rendered inside loops
so react considers that all child that you are rendering is one component unless you give them a different key
I'm currently fetching data in Component1, then dispatching an action to update the store with the response. The data can be seen in Component2 in this.props, but how can I render it when the response is returned? I need a way to reload the component when the data comes back.
Initially I had a series of functions run in componentDidMount but those are all executed before the data is returned to the Redux store from Component1. Is there some sort of async/await style between components?
class Component1 extends React.Component {
componentDidMount() {
this.retrieveData()
}
retrieveData = async () => {
let res = await axios.get('url')
updateParam(res.data) // Redux action creator
}
}
class Component2 extends React.Component {
componentDidMount() {
this.sortData()
}
sortData = props => {
const { param } = this.props
let result = param.sort((a,b) => a - b)
}
}
mapStateToProps = state => {
return { param: state.param }
}
connect(mapStateToProps)(Component2)
In Component2, this.props is undefined initially because the data has not yet returned. By the time it is returned, the component will not rerender despite this.props being populated with data.
Assuming updateParam action creator is correctly wrapped in call to dispatch in mapDispatchToProps in the connect HOC AND properly accessed from props in Component1, then I suggest checking/comparing props with previous props in componentDidUpdate and calling sortData if specifically the param prop value updated.
class Component2 extends React.Component {
componentDidMount() {
this.sortData()
}
componentDidUpdate(prevProps) {
const { param } = this.props;
if (prevProps.param !== param) { // <-- if param prop updated, sort
this.sortData();
}
}
sortData = () => {
const { param } = this.props
let result = param.sort((a, b) => a - b));
// do something with result
}
}
mapStateToProps = state => ({
param: state.param,
});
connect(mapStateToProps)(Component2);
EDIT
Given component code from repository
let appointmentDates: object = {};
class Appointments extends React.Component<ApptProps> {
componentDidUpdate(prevProps: any) {
if (prevProps.apptList !== this.props.apptList) {
appointmentDates = {};
this.setAppointmentDates();
this.sortAppointmentsByDate();
this.forceUpdate();
}
}
setAppointmentDates = () => {
const { date } = this.props;
for (let i = 0; i < 5; i++) {
const d = new Date(
new Date(date).setDate(new Date(date).getDate() + i)
);
let month = new Date(d).toLocaleString("default", {
month: "long"
});
let dateOfMonth = new Date(d).getDate();
let dayOfWeek = new Date(d).toLocaleString("default", {
weekday: "short"
});
// #ts-ignore
appointmentDates[dayOfWeek + ". " + month + " " + dateOfMonth] = [];
}
};
sortAppointmentsByDate = () => {
const { apptList } = this.props;
let dates: string[] = [];
dates = Object.keys(appointmentDates);
apptList.map((appt: AppointmentQuery) => {
return dates.map(date => {
if (
new Date(appt.appointmentTime).getDate().toString() ===
// #ts-ignore
date.match(/\d+/)[0]
) {
// #ts-ignore
appointmentDates[date].push(appt);
}
return null;
});
});
};
render() {
let list: any = appointmentDates;
return (
<section id="appointmentContainer">
{Object.keys(appointmentDates).map(date => {
return (
<div className="appointmentDateColumn" key={date}>
<span className="appointmentDate">{date}</span>
{list[date].map(
(apptInfo: AppointmentQuery, i: number) => {
return (
<AppointmentCard
key={i}
apptInfo={apptInfo}
/>
);
}
)}
</div>
);
})}
</section>
);
}
}
appointmentDates should really be a local component state object, then when you update it in a lifecycle function react will correctly rerender and you won't need to force anything. OR since you aren't doing anything other than computing formatted data to render, Appointments should just call setAppointmentDates and sortAppointmentsByDate in the render function.
I'm trying to display a list of product features from some local JSON data using React, but my map/list functionality isn't displaying anything onto the DOM.
I originally had imported react-render-html, but it wasn't compatible so I had to remove it.
Here's my productHighlights:
class ProductHighlights extends Component {
constructor(props) {
super(props);
this.state = {
data: null
}
}
componentWillReceiveProps(newProps) {
const index = newProps.selected;
const productData = Number.isInteger(index) ? newProps.productData[index] : null;
if (productData !== null) {
this.setState({ data: productData });
}
}
getFeatureList = (itemDescription) => {
itemDescription[0].features.map((feature, index) => (
<li key={index}>{feature}</li>
))
}
render() {
const itemDescription = this.state.data ? this.state.data.ItemDescription : null;
const featureList = itemDescription ? this.getFeatureList(itemDescription) : null;
console.log(itemDescription);
console.log('item description = ' + itemDescription);
return (
<div className="product-highlights-container">
<div className="product-highlights-title">product highlights</div>
<ul className="product-features">
{featureList}
</ul>
</div>
)
}
}
export default ProductHighlights;
Logging itemDescription to the screen shows [{...}], which then opens to > 0 > 'features: Array(10). I'm not sure whygetFeaturedList` isn't getting this info successfully.
What is the error message you are getting in the console?
Your map is implicitly returning undefined. You need to return the element.
itemDescription[0].features.map((feature, index) =>
{
return (<li key={index}>{feature}</li>);
}
)
I got it to display - all I had to do was add spaces around feature in this line in my getFeatureList function:
<li key={index}>{ feature }</li>
Only thing now is that the tags are displaying in the list. I may need to parse.
I want to iterate through each element in the array and display it in the breadcrumb navigation.
What i am trying to do?
from a particular path or location say /list/item_id and if the item has certain information my breadcrumb navigation should change to the hierarchy of information.
For example, say i have the information of the item stored in item_information...and it is array of objects as below,
const item_information = [
{
name: "c_name",
},
{
name: "a_name",
},
{
name: "name",
}
I want to retreive only the name of each object and store it in variable display and want to display that in the breadcrumb navigation....so to loop through each name value from the variable display i use .map function. In doing so , i get an error .map is not a function.
Below is the code,
class Crumb extends React.PureComponent {
render = () => {
const link = this.props.link;
let display;
let match;
let after_link;
if (link === '/') {
display = 'Home';
} else if (match = link.match(/^\/list\/new$/)) {
display = 'new item';
} else if (match = link.match(/^\/list\/([^/]+)$/))
if (this.props.item_information > 0) {
display = this.props.item_information.map((el) => {
return el.name;
});
} else {
const model_id = match[1];
const model = this.props.models && this.props.models.find(model
=> '' + model.id === model_id);
display = itemname;
after_link = 'after_link';
}
}
//last part of the link
if (!display) {
const parts = link.split('/');
display = parts[parts.length - 1];
}
return (
<div>
{Array.isArray(display) && display.map((display) => {
return (
<div className="crumb">
<Link to={link}>{display}</Link>
</div>
);
})}
<div className="crumb">
<Link to={link}>{display}</Link>
</div>
{after_link}</div>
);
};
}
class Breadcrumb extends React.PureComponent {
render = () => {
const path = this.props.location.pathname;
const crumbs = [];
path.split('/').slice(1).forEach((part, index, parts) => {
crumbs.push('/' + parts.slice(0, index + 1).join('/'));
});
return (
<div className="breadcrumb">
{crumbs.map((link, i) => {
return (
<Fragment key={link}>
<Crumb
item_information={this.props.item_information}/>
</Fragment>);
})}
</div>
);
};
}
Could someone help me in getting rid off the error .map is not a function. thanks.
I have a weird problem with React-Redux app. One of my components doesn't re-render on props change which is updated by action 'SET_WINNING_LETTERS'. github repo: https://github.com/samandera/hanged-man
setWord.js
const initialWordState = {
word: []
};
const setWinningLetters = (wordProps) => {
let {word, pressedKey} = wordProps;
for (let i = 0; i < word.length; i++) {
if (/^[a-zA-Z]$/.test(pressedKey) && word[i].letter.toUpperCase() == pressedKey) {
word[i].visible = true;
}
}
return {word};
}
const setWord = (state = initialWordState, action) => {
switch(action.type) {
case 'SET_WORD': return Object.assign({}, state, getWord(action.word));
case 'SET_WINNING_LETTERS': return Object.assign({}, state,
updateWord(action.wordProps));
}
return state;
}
export default setWord;
In Index.js in this function the actions are triggered
handleKeyPress(pressedKey) {
store.dispatch({
lettersProps: {
word:this.props.word,
pressedKey,
missedLetters: this.props.missedLetters
},
type: 'SET_MISSED_LETTERS'
});
store.dispatch ({
wordProps: {
word:this.props.word,
pressedKey
},
type: 'SET_WINNING_LETTERS'
});
this.showEndGame(this.props.word,this.props.missedLetters);
};
componentWillMount() {
fetchWord(this.statics.maxWordLength);
window.onkeydown = () =>
{this.handleKeyPress(String.fromCharCode(event.keyCode))};
}
And in PrimaryContent.js Winning and Missing Characetrs are rendered
import React from 'react';
import {connect} from 'react-redux';
import store from '../reducers/store';
import Hangedman from './Hangedman';
import AspectRatio from './AspectRatio';
import Puzzle from './Puzzle';
import MissedCharacters from './MissedCharacters';
const mapStateToProps = (store) => {
return {
word: store.wordState.word,
missedLetters: store.missedLettersState.missedLetters
}
}
class PrimaryContent extends React.Component {
constructor() {
super();
}
renderDisabledPuzzles(amount){
return Array.from({length: amount}, (value, key) => <AspectRatio parentClass="disabled" />)
}
renderLetters(word) {
return word.map(function(letterObj, index) {
let space = (letterObj.letter==' ' ? "disabled": '')
return(
<AspectRatio parentClass={space} key={"letter" + index}>
<div id={"letter" + index}>{letterObj.visible ? letterObj.letter : ''}</div>
</AspectRatio>
)
}) ;
}
render() {
let disabledCount = this.props.puzzles - this.props.word.length;
let disabledPuzzles = this.renderDisabledPuzzles(disabledCount);
let WinningLetters = this.renderLetters(this.props.word);
return (
<div className="ratio-content primary-content">
<Hangedman/>
<MissedCharacters missedLetters={this.props.missedLetters}/>
<Puzzle>
{disabledPuzzles}
{WinningLetters}
</Puzzle>
</div>
);
}
}
export default connect(mapStateToProps)(PrimaryContent);
MissedCharacters works well while {WinningLetters} doesn't.
The action 'SET_MISSED_LETTERS' works perfect, while 'SET_WINNING_LETTERS' works only when 'SET_MISSED_LETTERS' gets updated. It means when I press one or more letter that wins they won't display until I press the letter that is missing. When I press the missing letter the component that is parent for both missing and winning letters re-renders. I was trying to pass props to PrimaryContent from it's parent but I get the same. I tried to separate {WinningLetters} in it's own component wit access to redux store but it works even worse and stops updating even when MissedCharacters updates. Can you detect where I've made a mistake?