ReactJS simple hover works critical slow - reactjs

Here's whole class . This example works. No error in ts compiler no errors in browser logs .
import * as React from "react";
import { Label } from "../../components/label/label";
import IApp from "../../globals/interfaces";
import { Tap } from "../../globals/types";
import Services from "../../services/services";
import { HeaderI, HeaderStateI } from "./interface";
import { getMenuStyle, getMyStyle } from "./style";
export class Header extends React.Component<HeaderI, HeaderStateI, any> {
public add = Services.addElement;
private myRef: React.RefObject<HTMLDivElement>;
private myDOM: Element | Text;
constructor(args: any) {
super(args);
this.state = {
enabledComponent: true,
visibility: true,
debugView: false,
elements: [],
myStyle: getMyStyle(),
menuStyle: getMenuStyle(),
menuItemStyle: [getMenuStyle(), getMenuStyle()],
menuIsOpen: false,
myEvent: null,
};
this.myRef = React.createRef();
this.add = this.add.bind(this);
}
// Override func
public componentDidMount() {
this.myDOM = this.myRef.current;
}
public render() {
if ( this.state.visibility === true ) {
return (
<div ref={this.myRef} style={this.state.myStyle} >
<Label myStyle={this.state.menuStyle}
name="headerName"
text="MENU"
onClick={this.clickEvent.bind(this)}
onMouseEnterHandler={this.hoverIn.bind(this)}
onMouseLeaveHandler={this.hoverOut.bind(this)} />
{this.state.elements.map((element: React.ReactElement<any>, index) => {
return <span ref={this.myRef} key={index} style={this.state.menuItemStyle[index]}
onClick={this.clickMenuItem.bind(this)}>{element}</span>;
})}
</div>
);
}
}
// Override func
public componentDidUpdate(prevProps: any, prevState: any) {
// Typical usage (don't forget to compare props):
}
// Override func
public componentWillUpdate(nextProps: any, nextState: any) {
// We should not call setState !
// if (nextState.open == true && this.state.open == false) {
// this.props.onWillOpen();
// }
}
private adaptCss(e: CustomEvent) {
// DEMO for css changes :
// Collect (this of class instance)
const self = e.detail.data.self;
// Make any changes in css
// Collect base or initial css
const myStyle = getMyStyle();
// Make changes
myStyle.background = "yellow";
// Setup state and nothing more
self.setState({
myStyle,
});
}
private printMe() {
// console.log("Layout Header is active and update is on");
}
private clickEvent(event: MouseEvent | TouchEvent) {
if (this.state.menuIsOpen === false) {
const myKey = "header.01";
const element1Args: IApp.NewElementArgsI = {
key: myKey,
onClick: null,
myStyle: null,
content: "HOME",
hoverIn: ((e) => this.hoverIn(e, myKey)),
hoverOut: ((e) => this.hoverOut(e, myKey)),
};
const myKey2 = "header.02";
const element2Args: IApp.NewElementArgsI = {
key: myKey2,
onClick: null,
myStyle: null,
content: "ABOUT",
hoverIn: ((e) => this.hoverIn(e, myKey2)),
hoverOut: ((e) => this.hoverOut(e, myKey2)),
};
this.add(element1Args);
this.add(element2Args);
// Set new state for menu
this.setState(
{menuIsOpen: !this.state.menuIsOpen},
);
} else {
// Menu is already visible , delete menu items
this.setState (
{
menuIsOpen: !this.state.menuIsOpen,
elements: [],
},
);
}
}
private clickMenuItem(event: MouseEvent | TouchEvent) {
const t = event.target as HTMLDivElement;
// Also possible to call event.target.textContent !
switch (t.textContent) {
case "HOME":
this.props.provide({instruction: "show_home"});
break;
case "ABOUT":
this.props.provide({instruction: "show_about"});
break;
default:
console.warn("No case for cleckMenuItem in bodyCOntent class!");
}
}
private hoverIn = (e: Tap, id: any) => {
const styleArrayCopy = JSON.parse(JSON.stringify(this.state.menuItemStyle));
this.state.elements.forEach((element: React.ReactElement<any>, index: number) => {
if (id === element.key) {
styleArrayCopy[index].color = "red";
} else {
styleArrayCopy[index].color = "initial";
}
});
if (this.state.elements.length === 0) {
const test = getMenuStyle();
test.color = "lime";
this.setState({
menuItemStyle: styleArrayCopy,
menuStyle: test,
});
} else {
this.setState({
menuItemStyle: styleArrayCopy,
});
}
}
private hoverOut = (e: Tap, id: any) => {
this.state.elements.forEach((element: React.ReactElement<any>, index: number) => {
if (id === element.key) {
const styleArrayCopy = JSON.parse(JSON.stringify(this.state.menuItemStyle));
styleArrayCopy[index].color = "initial";
this.setState({
menuItemStyle: styleArrayCopy,
});
}
});
}
}
I get style like that :
export function getMenuStyle(): IApp.MyMinimumCssInterface {
return {
display: "block",
background: "#445566",
height: "30px",
width: "100%",
textAlign: "center",
color: "inherits",
} as IApp.MyMinimumCssInterface;
}
On hover in and out i have ~400ms delay . If i moving up/down there is no hover effect. This is so bad. What will be when i add large assets add more code...
I detect very slow executing in not just this example even most simple example from reactJS tutorials ?!
I am also interested in React.ReactElement Object instance . Is it possible for OverRide some func - Like updateDid ?
To missunderstund from comment This is from React site :
The style attribute accepts a JavaScript object with camelCased
properties rather than a CSS string. This is consistent with the DOM
style JavaScript property, is more efficient, and prevents XSS
security holes. For example:
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
I use it the same principle.
Updated , now looks like :
// Render
{this.state.elements.map((element: React.ReactElement<any>, index) => {
return <span ref={this.myRef} key={index} style={this.getStyle(element, index)} onClick={this.clickMenuItem.bind(this)}>{element}</span>;
})}
private hoverIn = (e: Tap, id: any) => {
const local: boolean[] = [];
this.state.elements.forEach((element: React.ReactElement<any>, index: number) => {
if (id === element.key) {
local.push(true);
} else {
local.push(false);
}
});
Any explanation ??

I finally found solution for very slow work flows.
There is no connections with React at all.
I have global.css who's loading on begin. First i noticed that only text delay.
I create text shadow and i most suspect on shadow but it was transition.
This code is most expensively code was :
-webkit-transition: color 300ms cubic-bezier(0.42, 0, 0.58, 1);
-moz-transition: color 300ms cubic-bezier(0.42, 0, 0.58, 1);
-o-transition: color 300ms cubic-bezier(0.42, 0, 0.58, 1);
transition: color 300ms cubic-bezier(0.42, 0, 0.58, 1);
I remove this part of code.
Maybe this answer will help somebody .

Related

react-modal: How to get modal to auto-expand when react-datepicker is clicked? (code attached)

How to get modal to auto-expand when react-datepicker is clicked? Modal fits initially, but then when you click on the date field and the react-datepicker shows the calendar, the react-modal dialog does not auto-expand?
Before:
Ater clicking date:
Code:
import React, { useState } from 'react';
import Modal from 'react-modal';
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
const customStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
},
};
export default function TestPage2() {
const [modalIsOpen, setIsOpen] = React.useState(false);
const [startDate, setStartDate] = useState(new Date());
function openModal() {
setIsOpen(true);
}
function closeModal() {
setIsOpen(false);
}
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
contentLabel="Example Modal"
style={customStyles}
>
<h3>Prior to Date Picker</h3>
<label htmlFor="Checkin Date">Checkin Date</label>
<DatePicker
selected={startDate}
// wrapperClassName="datePicker"
// className="form-control"
dateFormat="d MMMM yyyy"
name="checkinDate"
onChange={(date, event) => {
if (date) {
setStartDate(date)
}
}}
/>
<h3>After Date Picker</h3>
</Modal>
</div>
);
}
add the following property to your content object inside customStyles:
overflow: 'hidden'
and change the property values of react-datepicker-popper class :
.react-datepicker-popper {
position: static!important;
transform: none!important;
}
codesandbox: https://codesandbox.io/s/awesome-feather-mj81tz
I exactly faced the same issue a few weeks ago. I was looking for a easy fix but I' didn't found one. What I did is to split up the Datepicker into 2 components (+ redux).
Is your input <Datepicker />
Is your floating date picker <DatepickerFloatingItem/>
Datepicker
The datepicker is simply the input field and that's the component you can use throughout your react webapp. The biggest change here is that you need to run an action with the dispatcher to show the floating item. Additionally you need to determine the X and Y coordinates of your Datepicker to place the Floating Item at the correct spot.
Here is how the Datepicker component could look like (I've deleted the logic code to not confuse everyone):
class DatetimePicker extends React.Component<IDatetimePickerProps, IDatetimePickerState> {
public componentDidMount() {
this.updateDate(this.props.date);
window.addEventListener("resize", this.updateDimensions);
}
public componentWillUnmount() {
// display the floating datepicker even when resizing the window
window.removeEventListener("resize", this.updateDimensions);
}
///
/// LOGIC CODE (deleted)
///
private showCalendar = (): void => {
const { date, key } = this.state;
const { dispatch } = this.props;
const { showFloating } = this.props.datePickerState
if (!showFloating) {
this.createOutsideClickEvent();
if (date) {
this.updateDate(date);
}
} else {
this.removeOutsideClickEvent();
}
var boundingRect = document.getElementById(key)?.getBoundingClientRect();
if (boundingRect) {
dispatch(updateDatepickerData({ updateDate: this.updateDate, showFloating: true, date, positionX: boundingRect.left, positionY: boundingRect.top + boundingRect.height, key }))
}
}
private updateDimensions = (): void => {
const { dispatch } = this.props;
const { date, showFloating } = this.props.datePickerState;
const { key } = this.state;
var boundingRect = document.getElementById(key)?.getBoundingClientRect();
if (boundingRect && this.props.datePickerState.key === key) {
dispatch(updateDatepickerData({ positionX: boundingRect.left, positionY: boundingRect.top + boundingRect.height, date, showFloating }))
}
}
public render(): React.ReactNode {
const { input, wrapperRef, key } = this.state;
const { style, disabled, className, onClick, styleWrapper, icon } = this.props;
return <span className="datetimepicker" ref={wrapperRef} onClick={onClick} style={styleWrapper}>
<Input
className={`datetimepicker__input ${className}`}
value={input}
onChange={this.updateInput}
getFocus={this.disableErrorView}
getBlur={this.textInputLostFocus}
rightButtonClicked={this.showCalendar}
style={style}
id={key}
icon={icon}
disabled={disabled}
/>
</span>
}
}
DatepickerFloatingItem
You only need to position the DatepickerFloatingItem once in your application.
It's best to position it at App.js (the root component).
It's also important to have position: relative for the parent element and define position: fixed for the DatepickerFloatingItem. Now you can easily position your floating element using top: and left: with the coordinates of the Datepicker
And this is how the DatepickerFloatingItem could look like (I also removed the unnecessary code to keep it more understandable)
interface IDatepickerFloatingItemStateProps {
date: Date
showFloating: boolean
positionX?: number
positionY?: number
}
class DatepickerFloatingItem extends React.Component<IDatepickerFloatingItemProps, IDatepickerFloatingItemState> {
private clickedFloatingDatepicker = (event: React.MouseEvent<HTMLSpanElement>): void => event.preventDefault()
private updateDate = (date?: Date, closeFloating?: boolean): void => {
const { dispatch } = this.props;
dispatch(updateDatepickerDate(date ? date : new Date()))
if (closeFloating) dispatch(updateDatepickerShowFloating(!closeFloating))
}
public render(): React.ReactNode {
const { showFloating, date, positionX, positionY } = this.props;
const { datepickerView } = this.state;
return <span className={`datetimepicker__floating ${showFloating ? "show" : ""}`} onClick={this.clickedFloatingDatepicker} style={{ top: `${positionY}px`, left: `${positionX}px` }}>
<DateCalendar datepickerView={datepickerView} date={date} updateDate={this.updateDate} />
</span>
}
}
function mapStateToProps(applicationState: ApplicationState): IDatepickerFloatingItemStateProps {
return {
date: applicationState.datepicker.date,
showFloating: applicationState.datepicker.showFloating,
positionX: applicationState.datepicker.positionX,
positionY: applicationState.datepicker.positionY
}
}
export default connect(mapStateToProps)(DatepickerFloatingItem)
Redux
I had to move some stuff to the Redux store to ensure that the FloatingDatePicker as well as the Datepicker have chance to communicate somehow
I've kept the redux store pretty straight forward:
import { Action, Reducer } from 'redux';
export interface DatepickerState {
date: Date
showFloating: boolean
positionX?: number
positionY?: number
updateDate?: (date?: Date) => void
}
export const UPDATE_DATEPICKER_SHOWFLOATING = "UPDATE_DATEPICKER_SHOWFLOATING";
export const UPDATE_DATEPICKER_DATA = "UPDATE_DATEPICKER_DATA";
export const UPDATE_DATEPICKER_DATE = "UPDATE_DATEPICKER_DATE";
export interface UpdateDatepickerShowFloating {
type: "UPDATE_DATEPICKER_SHOWFLOATING"
showFloating: boolean
}
export interface UpdateDatepickerDate {
type: "UPDATE_DATEPICKER_DATE"
date: Date
}
export interface UpdateDatepickerData {
type: "UPDATE_DATEPICKER_DATA"
state: DatepickerState
}
type KnownAction = UpdateDatepickerShowFloating | UpdateDatepickerData | UpdateDatepickerDate
const unloadedState: DatepickerState = { updateDate: () => { }, date: new Date(), showFloating: false, showTime: false, positionX: 0, positionY: 0 }
export const reducer: Reducer<DatepickerState> = (state: DatepickerState | undefined, incomingAction: Action): DatepickerState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case UPDATE_DATEPICKER_SHOWFLOATING:
return { ...state, showFloating: action.showFloating }
case UPDATE_DATEPICKER_DATE:
setTimeout(() => { if (state.updateDate) state.updateDate(action.date) }, 1)
return { ...state, date: action.date }
case UPDATE_DATEPICKER_DATA:
return { ...state, ...action.state }
default:
break;
}
return state;
}
And as you can see at the image, it's actually working inside a modal:
I know this approach is pretty time consuming, but still I hope it was still helping you somehow and I also hope your eyes don't burn from seeing class based components.

How to scroll up history in React chat page

*Trying to show a chat history with infinite reload similar to Skype or any popular chat app
In a chat page. If my chat messages limit is 10 messages.
And the chat has 30.
It will show latest 10 when I load the chat.
When I scroll to the top I want to see the previous 10.
I tried this myself and the scroll position stays the same but the messages load in the view.
It should load to the top and hold the scroll position.
How can this be done?
Here's my page:
https://pastebin.com/36xZPG1W
import React, { useRef, useState, useEffect } from 'react';
import produce from 'immer';
import dayjs from 'dayjs';
import { WithT } from 'i18next';
import * as ErrorHandler from 'components/ErrorHandler';
import useOnScreen from 'utils/useOnScreen';
import getLang from 'utils/getLang';
import Message from './Message';
const limit = 10;
const lang = getLang();
interface IMessagesProps extends WithT {
messages: any;
currentUserID: string;
chatID: string;
fetchMore: any;
typingText: any;
setSelectedMsg: any;
removeMessage: any;
}
const Messages: React.FC<IMessagesProps> = ({
messages,
currentUserID,
chatID,
fetchMore,
setSelectedMsg,
removeMessage,
t,
}) => {
const elementRef = useRef(null);
const isOnScreen = useOnScreen(elementRef);
const topElementRef = useRef(null);
const topIsOnScreen = useOnScreen(topElementRef);
const isUserInside = useRef(true);
const scroller = useRef<HTMLDivElement>(null);
const messagesEnd = useRef<HTMLDivElement>(null);
const [hasMore, setHasMore] = useState(true);
useEffect(() => {
scrollToBottom();
}, []);
useEffect(() => {
autoscroll();
}, [messages]);
//NOT WORKING
const autoscroll = () => {
// Visible height
const visibleHeight = scroller.current.offsetHeight;
// Height of messages container
const containerHeight = scroller.current.scrollHeight;
// How far have I scrolled?
const scrollOffset = scroller.current.scrollTop + visibleHeight;
// New message element
const firstChild = scroller.current.firstElementChild;
console.log(`visibleHeight`, visibleHeight);
console.log(`containerHeight`, containerHeight);
console.log(`scrollOffset`, scrollOffset);
console.log(`firstChild`, firstChild.offsetHeight);
console.log(`firstChild`, firstChild.scrollHeight);
console.log(`firstChild`, firstChild);
scroller.current.scrollTop = scrollOffset;
// // Height of the new message
// const newMessageStyles = getComputedStyle($newMessage)
// const newMessageMargin = parseInt(newMessageStyles.marginBottom)
// const newMessageHeight = $newMessage.offsetHeight + newMessageMargin
// // Visible height
// const visibleHeight = $messages.offsetHeight
// // Height of messages container
// const containerHeight = $messages.scrollHeight
// // How far have I scrolled?
// const scrollOffset = $messages.scrollTop + visibleHeight
// if (containerHeight - newMessageHeight <= scrollOffset) {
// $messages.scrollTop = $messages.scrollHeight
// }
};
const fetchDataForScrollUp = cursor => {
ErrorHandler.setBreadcrumb('fetch more messages');
if (!hasMore) {
return;
}
fetchMore({
variables: {
chatID,
limit,
cursor,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult?.getMessages || fetchMoreResult.getMessages.messages.length < limit) {
setHasMore(false);
return previousResult;
}
const newData = produce(previousResult, draftState => {
draftState.getMessages.messages = [...previousResult.getMessages.messages, ...fetchMoreResult.getMessages.messages];
});
return newData;
},
});
};
if (messages?.length >= limit) {
if (topIsOnScreen) {
fetchDataForScrollUp(messages[messages.length - 1].id);
}
}
if (isOnScreen) {
isUserInside.current = true;
} else {
isUserInside.current = false;
}
const scrollToBottom = () => {
if (messagesEnd.current) {
messagesEnd.current.scrollIntoView({ behavior: 'smooth' });
}
};
const groupBy = function (arr, criteria) {
return arr.reduce(function (obj, item) {
// Check if the criteria is a function to run on the item or a property of it
const key = typeof criteria === 'function' ? criteria(item) : item[criteria];
// If the key doesn't exist yet, create it
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
obj[key] = [];
}
// Push the value to the object
obj[key].push(item);
// Return the object to the next item in the loop
return obj;
}, {});
};
const objectMap = object => {
return Object.keys(object).reduce(function (result, key) {
result.push({ date: key, messages: object[key] });
return result;
}, []);
};
const group = groupBy(messages, datum => dayjs(datum.createdAt).locale(lang).format('dddd, MMMM D, YYYY').toLocaleUpperCase());
const messageElements = objectMap(group)
.reverse()
.map((item, index) => {
const messageElements = item.messages
.map(message => {
return (
<Message
key={uniqueKey}
message={message}
currentUserID={currentUserID}
lang={lang}
removeMessage={removeMessage}
t={t}
chatID={chatID}
setSelectedMsg={setSelectedMsg}
/>
);
})
.reverse();
return messageElements;
})
.reduce((a, b) => a.concat(b), []);
return (
<div style={{ marginBottom: '25px' }}>
<div ref={topElementRef} />
<div
style={{
position: 'relative',
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
height: '100%',
overflow: 'hidden',
}}
ref={scroller}
>
{messageElements}
<div ref={elementRef} style={{ position: 'absolute', bottom: '5%' }} />
</div>
</div>
);
};
export default Messages;
Been stuck on this for 2 weeks lol. Any advice is helpful :)
Have you tried scrollIntoView ? you can try after changing your autoscroll function like following
const autoscroll = () => {
elementRef.current.scrollIntoView({ behavior: 'smooth' })
};

How to properly setup Azure Media Player in React.js?

I'm currently integrating a React component with Azure Media Player. I followed the documentation and first, I added the required CDN urls to the index.html file. Then I added the sample code into the App. The problem is, it throws the error 'amp' is not defined no-undef
videoPlayer.js
class videoPlayer extends Component {
render () {
const myOptions = {
"nativeControlsForTouch": false,
controls: true,
autoplay: true,
width: "640",
height: "400",
}
const myPlayer = amp("azuremediaplayer", myOptions);
myPlayer.src([
{
"src": "https://devpflmedia-uswe.streaming.media.azure.net//d5f1a8b6-0d52-4e62-addc-aee7bafe408d/097cee43-6822-49bd-84f5-9f6efb05.ism/manifest",
"type": "application/vnd.ms-sstr+xml"
}
]);
return (
<video id="azuremediaplayer" class="azuremediaplayer amp-default-skin amp-big-play-centered" tabindex="0"></video>
)
}
}
How can I fix this?
When I use the amp this way, the mentioned on.progress callback works for me. Good luck!
import * as React from "react"
import loader from "./loader";
import { RefObject } from "react";
import './videoPlayer.css';
const DEFAULT_SKIN = "amp-flush";
const DEFAULT_RATIO = [16, 9];
const DEFAULT_OPTIONS = {
controls: true,
autoplay: true,
muted: true,
logo: {
enabled: false
},
};
declare const window: any;
export interface IVideoPlayerProps {
readonly src: { src: string; }[];
readonly options: any;
readonly skin: string;
readonly className: string;
readonly adaptRatio: Array<number>;
}
export default class VideoPlayer extends React.PureComponent<IVideoPlayerProps, {}> {
public static defaultProps = {
skin: DEFAULT_SKIN,
className: "",
adaptRatio: DEFAULT_RATIO,
options: DEFAULT_OPTIONS,
}
videoNode: RefObject<any>;
player: any;
initialization: any;
constructor(props: IVideoPlayerProps) {
super(props);
this.videoNode = React.createRef();
}
componentWillUnmount() {
this._destroyPlayer();
}
componentDidMount() {
const { skin } = this.props;
this.initialization = loader(skin).then(() => {
this._createPlayer();
this._setVideo();
});
}
componentDidUpdate(prevProps: IVideoPlayerProps) {
if (prevProps.src !== this.props.src) {
this.initialization.then(() => this._setVideo());
}
}
_destroyPlayer() {
this.player && this.player.dispose();
}
_setVideo() {
const { src } = this.props;
this.player.src(src);
}
_createPlayer() {
this.player = window.amp(this.videoNode.current, this.props.options);
this.player.on("progress", () => alert('on progress called'));
}
render(): JSX.Element {
return (
<div>
<video
ref={this.videoNode}
/>
</div>
);
}
}
Also the loader function - I use it this way since I may need to use the player in the (possible) offline environment.
export default (skin = 'amp-flush') => {
return new Promise((resolve, _) => {
if (document.querySelector('#amp-azure')) {
// video player is already rendered
return resolve()
}
let scriptTag = document.createElement('script')
let linkTag = document.createElement('link')
linkTag.rel = 'stylesheet'
scriptTag.id = 'amp-azure'
scriptTag.src = '//amp.azure.net/libs/amp/2.1.5/azuremediaplayer.min.js'
linkTag.href = `//amp.azure.net/libs/amp/2.1.5/skins/${skin}/azuremediaplayer.min.css`
document.body.appendChild(scriptTag)
document.head.insertBefore(linkTag, document.head.firstChild)
scriptTag.onload = () => resolve({ skin: skin })
})
}

Color office-ui-fabric-react DetailsList Cell by its value

I am using office-ui-fabric-react DetailsList in SPFx web part and it works fine. I want to have a background color of the cell based on the value in them. Say red for no and green for yes. Please help me
The DetailsList has a properties onRenderItemColumn. It points to the method of rendering the list.
My test demo:
import * as React from 'react';
import styles from './ReactSpfx1.module.scss';
import { IReactSpfx1Props } from './IReactSpfx1Props';
import { escape } from '#microsoft/sp-lodash-subset';
import { createListItems, IExampleItem } from '#uifabric/example-data';
import { Link } from 'office-ui-fabric-react/lib/Link';
import { Image, ImageFit } from 'office-ui-fabric-react/lib/Image';
import { DetailsList, buildColumns, IColumn } from 'office-ui-fabric-react/lib/DetailsList';
import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
export interface IDetailsListCustomColumnsExampleState {
sortedItems: IExampleItem[];
columns: IColumn[];
}
export default class ReactSpfx1 extends React.Component<IReactSpfx1Props, any> {
constructor(props) {
super(props);
const items = createListItems(10);
this.state = {
sortedItems: items,
columns: _buildColumns(items),
};
}
public render(): React.ReactElement<IReactSpfx1Props> {
const { sortedItems, columns } = this.state;
return (
<DetailsList
items={sortedItems}
setKey="set"
columns={columns}
onRenderItemColumn={_renderItemColumn}
onColumnHeaderClick={this._onColumnClick}
onItemInvoked={this._onItemInvoked}
onColumnHeaderContextMenu={this._onColumnHeaderContextMenu}
ariaLabelForSelectionColumn="Toggle selection"
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
checkButtonAriaLabel="Row checkbox"
/>
);
}
private _onColumnClick = (event: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const { columns } = this.state;
let { sortedItems } = this.state;
let isSortedDescending = column.isSortedDescending;
// If we've sorted this column, flip it.
if (column.isSorted) {
isSortedDescending = !isSortedDescending;
}
// Sort the items.
sortedItems = _copyAndSort(sortedItems, column.fieldName!, isSortedDescending);
// Reset the items and columns to match the state.
this.setState({
sortedItems: sortedItems,
columns: columns.map(col => {
col.isSorted = col.key === column.key;
if (col.isSorted) {
col.isSortedDescending = isSortedDescending;
}
return col;
}),
});
};
private _onColumnHeaderContextMenu(column: IColumn | undefined, ev: React.MouseEvent<HTMLElement> | undefined): void {
console.log(`column ${column!.key} contextmenu opened.`);
}
private _onItemInvoked(item: any, index: number | undefined): void {
alert(`Item ${item.name} at index ${index} has been invoked.`);
}
}
function _buildColumns(items: IExampleItem[]): IColumn[] {
const columns = buildColumns(items);
//console.log(columns)
const thumbnailColumn = columns.filter(column => column.name === 'thumbnail')[0];
// Special case one column's definition.
thumbnailColumn.name = '';
thumbnailColumn.maxWidth = 50;
thumbnailColumn.ariaLabel = 'Thumbnail';
//thumbnailColumn.className ="test";
return columns;
}
function _renderItemColumn(item: IExampleItem, index: number, column: IColumn) {
const fieldContent = item[column.fieldName as keyof IExampleItem] as string;
//you just need to change code here
switch (column.key) {
case 'thumbnail':
return <Image src={fieldContent} width={50} height={50} imageFit={ImageFit.cover} />;
case 'name':
return <Link href="#">{fieldContent}</Link>;
case 'color':
//fieldContent is the column value
if(fieldContent=="red"){
return (<span
data-selection-disabled={true}
className={mergeStyles({ color: fieldContent, height: '100%', display: 'block' ,background:fieldContent})}
>
{fieldContent}
</span>)
}else{
return (
<span
data-selection-disabled={true}
className={mergeStyles({ color: fieldContent, height: '100%', display: 'block' })}
>
{fieldContent}
</span>
);
}
default:
return <span>{fieldContent}</span>;
}
}
function _copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): T[] {
const key = columnKey as keyof T;
return items.slice(0).sort((a: T, b: T) =>
}
Test Result:

How to execute a function when some item renders in react native?

I have a sectionlist of Contacts where I am displaying both device and online contacts of a user. The online contacts api doesnt give me all the contacts at once. So I have to implement some pagination. I am also fetching all device contacts and first page of online contacts and sorting them to show in sectionlist, but the problem is, to load more contacts, i have to keep track of the last item rendered in my state and in the render function I am calling pagination function to load more contacts. and then i am updating the state of fetched online contact. But its an unsafe operation, is there a better way to achieve this?
I want to execute a function when the specific item renders and it can update the state.
Here is some code: ContactList.tsx
import React, { Component } from "react";
import {
View,
StyleSheet,
SectionListData,
SectionList,
Text
} from "react-native";
import { Contact } from "../../models/contact";
import ContactItem from "./contact-item";
export interface ContactsProps {
onlineContacts: Contact[];
deviceContacts: Contact[];
fetchMoreITPContacts: () => void;
}
export interface ContactsState {
loading: boolean;
error: Error | null;
data: SectionListData<Contact>[];
lastItem: Contact;
selectedItems: [];
selectableList: boolean;
}
class ContactList extends Component<ContactsProps, ContactsState> {
private sectionNames = [];
constructor(props: ContactsProps, state: ContactsState) {
super(props, state);
this.state = {
loading: false,
error: null,
data: [],
lastItem: this.props.onlineContacts[this.props.onlineContacts.length - 1]
};
for (var i = 65; i < 91; ++i) {
this.sectionNames.push({
title: String.fromCharCode(i),
data: []
});
}
}
private buildSectionData = contacts => {
this.sort(contacts);
const data = [];
const contactData = this.sectionNames;
contacts.map(contact => {
const index = contact.name.charAt(0).toUpperCase();
if (!data[index]) {
data[index] = [];
contactData.push({
title: index,
data: []
})
}
data[index].push(contact);
});
for (const index in data) {
const idx = contactData.findIndex(x => x.title === index);
contactData[idx].data.push(...data[index]);
}
this.setState({
loading: false,
error: null,
lastItem: contacts[contacts.length - 1],
data: [...contactData]
});
};
private sort(contacts) {
contacts.sort((a, b) => {
if (a.name > b.name) {
return 1;
}
if (b.name > a.name) {
return -1;
}
return 0;
});
}
componentDidMount() {
const contacts = [].concat(
this.props.deviceContacts,
this.props.onlineContacts
);
this.buildSectionData(contacts);
}
componentDidUpdate(
prevProps: Readonly<ContactsProps>,
prevState: Readonly<ContactsState>,
snapshot?: any
): void {
if (this.props.onlineContacts !== prevProps.onlineContacts) {
const from = this.props.itpContacts.slice(
prevProps.onlineContacts.length,
this.props.onlineContacts.length
);
this.buildSectionData(from);
}
}
renderItem(item: any) {
if (!!this.state.lastItem && !this.state.loading)
if (item.item.id === this.state.lastItem.id) {
this.setState({
loading: true
});
this.props.fetchMoreOnlineContacts();
}
return <ContactItem item={item.item} />;
}
render() {
return (
<View style={styles.container}>
<SectionList
sections={this.state.data}
keyExtractor={(item, index) => item.id}
renderItem={this.renderItem.bind(this)}
renderSectionHeader={({ section }) =>
section.data.length > 0 ? (
<Text style={styles.sectionTitle}>
{section.title}
</Text>
) : null
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
sectionTitle: {
paddingBottom: 30,
paddingLeft: 25,
fontWeight: "bold",
fontSize: 20
}
});
export default ContactList;
Yeah after some thoughts I got the answer may be.
instead of calling fetchMoreContacts from renderItem I passed the lastItem as a prop to the ContactItem component.
and in the constructor I checked If the item is lastItem and called to fetchMoreContact.
and it worked!

Resources