Why React function do not retuning div? - javascript

I am not getting div in my dom. there is div with id of teams when I inspect app in browser and function returning both console.logs but no divs inside #teams div. :S
const Teams = () => {
const homeTeam = [
{ position: 0, team: "home", visible: true, x: "30%", y: "30%" },
{ position: 1, team: "home", visible: true, x: "40%", y: "30%" },
];
const awayTeam = [
{ position: 0, team: "away", visible: true, x: "70%", y: "70%" },
{ position: 1, team: "away", visible: true, x: "60%", y: "70%" },
];
const setTeams = (home, away) => {
const setPlayers = (team) => {
team.map((player, index) => {
console.log(player);
return (
<div className={`player ${player.team === "home" ? "home" : "away"}`} key={index}>
{player.position === 0 ? "G" : null}
</div>
);
});
};
setPlayers(home);
setPlayers(away);
};
return <div id="teams">{setTeams(homeTeam, awayTeam)}</div>;
};
export default Teams;

setPlayers and setTeams function does not return anything. Hence you do not see those element. Do this instead.
const Teams = () => {
const homeTeam = [
{ position: 0, team: "home", visible: true, x: "30%", y: "30%" },
{ position: 1, team: "home", visible: true, x: "40%", y: "30%" },
];
const awayTeam = [
{ position: 0, team: "away", visible: true, x: "70%", y: "70%" },
{ position: 1, team: "away", visible: true, x: "60%", y: "70%" },
];
const setTeams = (home, away) => {
const team = [...home,...away];
return team.map((player, index)=>
<div className={`player ${player.team === "home" ? "home" : "away"}`} key={index}>
{player.position === 0 ? "G" : null}
</div>
);
};
return <div id="teams">{setTeams(homeTeam, awayTeam)}</div>;
};
export default Teams;

Main Issue is when you are using your two functions :
setPlayers(home);
setPlayers(away);
within your setPlayers function. The returned map template (JSX) is actually returned to the respective calling function which is setPlayers() function (calling two times with different arguments). And here the execution of your program basically ends. No JSX is returned to main component's return() method.
To make sure the the JSX is returned with proper mapped JSX, JSX needs to return to the setTeams(homeTeam, awayTeam) so that it is rendered in our app. I have made sure I use one function setTeams twice in the code with two arrays homeTeam and awayTeam
You shoudl try this code instead :)
const App = () => {
const homeTeam = [
{ position: 0, team: "home", visible: true, x: "30%", y: "30%" },
{ position: 1, team: "home", visible: true, x: "40%", y: "30%" }
];
const awayTeam = [
{ position: 0, team: "away", visible: true, x: "70%", y: "70%" },
{ position: 1, team: "away", visible: true, x: "60%", y: "70%" }
];
const setTeams = (team) => {
return team.map((player, index) => (
<div
className={`player ${player.team === "home" ? "home" : "away"}`}
key={index}
>
{player.position === 0 ? "G" : null}
</div>
));
};
return (
<div id="teams">
<h1>My APP</h1>
{setTeams(homeTeam)}
{setTeams(awayTeam)}
</div>
);
};
ReactDOM.render(<App/>,document.querySelector("#root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

You're not returning anything in your setPlayers and setTeams functions. You need to return team.map and setPlayers as well like so:
const setTeams = (home, away) => {
return setPlayers = (team) => {
return team.map((player, index) => {
console.log(player);
return (
<div className={`player ${player.team === "home" ? "home" : "away"}`} key={index}>
{player.position === 0 ? "G" : null}
</div>
);
});
};

Solution
const Teams = () => {
const [homeTeam] = useState([
{ position: 0, team: 'home', visible: true, x: '30%', y: '30%' },
{ position: 1, team: 'home', visible: true, x: '40%', y: '30%' }
]);
const [awayTeam] = useState([
{ position: 0, team: 'away', visible: true, x: '70%', y: '70%' },
{ position: 1, team: 'away', visible: true, x: '60%', y: '70%' }
]);
const setPlayers = useCallback(
(team) =>
team.map((player, index) => (
<div className={`player ${player.team === 'home' ? 'home' : 'away'}`} key={index}>
{player.position === 0 ? 'G' : null}
</div>
)),
[]
);
return (
<div>
{setPlayers(homeTeam)}
{setPlayers(awayTeam)}
</div>
);
};
export default Teams;

Related

How can I link an animation with a floating action button?

I want to do this:
This is the code from the video above:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:pomodoro/5.hourglass_animation/countdown_timer/responsive_web.dart';
class StartPomodoro extends StatefulWidget {
const StartPomodoro({
super.key,
});
//final DateTime end;
#override
State<StartPomodoro> createState() => _StartPomodoroState();
}
class _StartPomodoroState extends State<StartPomodoro>
with TickerProviderStateMixin {
final now = DateTime.now();
List<bool> isSelected = [true, false];
late Timer timer;
late AnimationController controller;
String get countText {
Duration count = controller.duration! * controller.value;
return controller.isDismissed
? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
double progress = 1.0;
bool LongBreak = true;
void notify() {
if (countText == '00:00:00') {}
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 0),
);
controller.addListener(() {
notify();
if (controller.isAnimating) {
setState(() {
progress = controller.value;
});
} else {
setState(() {
progress = 1.0;
LongBreak = true;
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor:
LongBreak ? const Color(0xffD94530) : const Color(0xff6351c5),
body: GestureDetector(
onTap: () {
if (controller.isDismissed) {
showModalBottomSheet(
context: context,
builder: (context) => SizedBox(
height: 300,
child: CupertinoTimerPicker(
initialTimerDuration: controller.duration!,
onTimerDurationChanged: (time) {
setState(() {
controller.duration = time;
});
},
),
),
);
}
},
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: const Color(0xffD94530),
height: controller.value * 580,
),
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
verticalDirection: VerticalDirection.up,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: ResponsiveWeb(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(
vertical: 2.0, horizontal: 15.0),
child: Container(
width: MediaQuery.of(context).size.width,
height: 210,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: const Color(0xffFAFAFA)),
child: Container(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Column(
children: [
Text(
"Hyper-focused on... (+add task)",
style: GoogleFonts.nunito(
fontSize: 20.0,
fontWeight:
FontWeight.w500,
),
),
],
),
),
),
const SizedBox(height: 10),
Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
SingleChildScrollView(
scrollDirection:
Axis.horizontal,
child: Row(
mainAxisAlignment:
MainAxisAlignment
.center,
children: [
Padding(
padding:
const EdgeInsets
.fromLTRB(
2, 0, 2, 0),
child: Text(
countText,
style: GoogleFonts
.nunito(
fontWeight:
FontWeight
.w500,
letterSpacing: 8,
fontSize: 57.0,
color: const Color(
0xff3B3B3B),
),
),
),
],
),
),
SingleChildScrollView(
scrollDirection:
Axis.horizontal,
child: Row(
mainAxisAlignment:
MainAxisAlignment
.start,
textDirection:
TextDirection.ltr,
mainAxisSize:
MainAxisSize.max,
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
Padding(
padding:
const EdgeInsets
.fromLTRB(
13, 0, 26, 0),
child: Text(
'Hours',
style: GoogleFonts
.nunito(
fontWeight:
FontWeight
.w500,
// letterSpacing:
// 2,
fontSize: 20.0,
color: const Color(
0xff3B3B3B),
),
),
),
Padding(
padding:
const EdgeInsets
.fromLTRB(
13, 0, 26, 0),
child: Text(
'Minutes',
style: GoogleFonts
.nunito(
fontWeight:
FontWeight
.w500,
// letterSpacing:
// 2,
fontSize: 20.0,
color: const Color(
0xff3B3B3B),
),
),
),
Padding(
padding:
const EdgeInsets
.fromLTRB(
0, 0, 0, 0),
child: Text(
'Seconds',
style: GoogleFonts
.nunito(
fontWeight:
FontWeight
.w500,
// letterSpacing:
// 2,
fontSize: 20.0,
color: const Color(
0xff3B3B3B),
),
),
),
],
),
),
],
),
),
),
],
),
),
),
),
const SizedBox(
height: 65,
),
Column(
// mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 15.0),
child:
FloatingActionButton.extended(
elevation: 0,
backgroundColor:
const Color(0xffFAFAFA),
onPressed: () {
if (controller
.isAnimating) {
controller.stop();
setState(() {
LongBreak = false;
});
} else {
controller.reverse(
from: controller
.value ==
0
? 1.0
: controller
.value);
setState(() {
LongBreak = false;
});
}
},
icon: Icon(
controller.isAnimating
? Icons.pause_outlined
: Icons
.play_arrow_outlined,
size: 24,
color: const Color(
0xff3B3B3B),
),
label: Text(
controller.isAnimating
? "Pause"
: "Start",
style: GoogleFonts.nunito(
fontWeight:
FontWeight.w500,
letterSpacing: 2,
fontSize: 24.0,
color: const Color(
0xff3B3B3B),
),
)),
);
}),
],
),
],
),
),
),
],
),
],
);
}),
),
),
);
}
AnimationController _buildClockAnimation(TickerProvider tickerProvider) {
return AnimationController(
vsync: tickerProvider,
duration: const Duration(milliseconds: 750),
);
}
void _animateLeftDigit(
int prev,
int current,
AnimationController controller,
) {
final prevFirstDigit = (prev / 10).floor();
final currentFirstDigit = (current / 10).floor();
if (prevFirstDigit != currentFirstDigit) {
controller.forward();
}
}
}
And I decided to optimize the timer by adding a setting function. But it is difficult for me to add the animation, but the timer works fine. The only thing that doesn't work is the animation when I click “start”
This is what I mean:
Please click on the youtube video
https://www.youtube.com/watch?v=cuAS_Pk5cNk&ab_channel=lomipac
This is the code from the button of the second video above, where I want to initialize the animation:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../controllers/countdown_controller.dart';
class StartStopGroupButton extends StatefulWidget {
const StartStopGroupButton({Key? key}) : super(key: key);
#override
State<StartStopGroupButton> createState() => _StartStopGroupButtonState();
}
class _StartStopGroupButtonState extends State<StartStopGroupButton>
with TickerProviderStateMixin {
String startPausedText = 'Start'.tr;
String stopText = 'stop'.tr;
final CountDownController _countDownController = Get.find();
#override
Widget build(BuildContext context) {
return Obx(() => Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 15.0),
child: FloatingActionButton.extended(
heroTag: 'btn1',
elevation: 0,
backgroundColor: const Color(0xffFAFAFA),
onPressed: _countDownController.startPaused,
label: Text(
_countDownController.startPausedText.value,
style: GoogleFonts.nunito(
fontWeight: FontWeight.w500,
fontSize: 24.0,
color: const Color(0xff3B3B3B),
),
),
),
),
],
),
));
}
}
How can I link the animation to the start button from the second code?
Thanks for any help you can provide

React Native - Changing the indicator width according to tab bar text width (react-native-tab-view)

I am using react-native-tab-view, and trying to change the indicator width. I would like indicator width to be the same with the tab text. But What I did is just the default. I have tried in many ways, but always it gave me the wrong result. The tab bar should be scrollable horizontally as well. Could you check which part I should change?
This is the expected result :
ShowAllIndex Code :
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.dark,
},
});
const ShowAllIndex = () => {
const { seller } = useSelector((s) => s.auth, shallowEqual);
const [routes] = useState([
{ key: 'best', title: 'BEST' },
{ key: 'jacket', title: '아우터' },
{ key: 'pants', title: '바지' },
{ key: 'skirts', title: '스커트' },
{ key: 'topClothe', title: '원피스' },
{ key: 'one', title: '바지' },
{ key: 'two', title: '스커트' },
{ key: 'three', title: '상의' },
]);
const renderScene = SceneMap({
best: ShowAllMainRoutes,
jacket: JacketRoutes,
pants: PantsRoutes,
skirts: SkirtsRoutes,
topClothe: TopClotheRoutes,
one: ShowAllMainRoutes,
two: JacketRoutes,
three: PantsRoutes,
});
return (
<ScrollView style={[styles.container, { marginTop: Global() }]}>
<CustomTabView
routes={routes}
renderScene={renderScene}
scrollEnabled={true}
tabStyle={{ width: 'auto' }}
showAll={true}
/>
</ScrollView>
);
};
export default ShowAllIndex;
CustomTabView code :
const initialLayout = { width: Dimensions.get('window').width };
const CustomTabView = ({
routes,
renderScene,
numberOfTabs,
indicatorWidth,
scrollEnabled = false,
tabStyle,
showAll,
indicatorStyle,
}) => {
const [index, setIndex] = useState(0);
const renderTabBar = (props) => (
<TabBar
{...props}
scrollEnabled={scrollEnabled}
indicatorStyle={[
indicatorStyle,
{
backgroundColor: colors.barbie_pink,
height: 2.5,
bottom: -1,
},
]}
style={[styles.tabBar]}
renderLabel={({ route, focused }) => {
return (
<Text
style={[styles.label, focused ? styles.activeLabel : styles.label]}
>
{route.title}
</Text>
);
}}
tabStyle={tabStyle}
/>
);
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
renderTabBar={renderTabBar}
onIndexChange={setIndex}
initialLayout={initialLayout}
style={[styles.container]}
/>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: colors.dark,
},
scene: {
flex: 1,
marginTop: 5,
},
tabBar: {
backgroundColor: 'transparent',
shadowOpacity: 0,
elevation: 0,
borderBottomWidth: 0.5,
borderColor: colors.very_light_pink_two,
marginBottom: 5,
},
label: {
color: colors.very_light_pink_four,
fontSize: 14,
lineHeight: 20.8,
fontFamily: 'NotoSansCJKkr-Regular',
letterSpacing: -0.35,
},
activeLabel: {
color: colors.barbie_pink,
},
});
Thank you for answers!
Check this solution:
const TAB_MARGIN = 24;
<TabBar
...
scrollEnabled
renderIndicator={indicatorProps => {
const width = indicatorProps.getTabWidth(this.state.index) - TAB_MARGIN
return <TabBarIndicator {...indicatorProps} width={width} />
}}
indicatorStyle={{
backgroundColor: '#333',
height: 4,
left: TAB_MARGIN / 2,
}}
...
/>
I think the prop indicatorStyle in TabBar that can resolves you problem.
You can do the following:
<TabBar
scrollEnabled //add this line to make it scrollable
tabStyle={{width: 100}} //and this one to change the tab width
/>

How to add a custom horizontal scroll event to a React material table

I'm trying to implement a slider with arrow icons that allow the user to scroll the material table horizontally. I already did the slider component, but the scroll events in the arrows aren't working yet. I added a tableRef to the material table and tried to use tableEl.current.scrollTo() function, but, unfortunately, it isn't working. So How can I create the eventHandlers to scroll the table horizontally?
My custom material table
My slider component:
import React from 'react';
import ArrowBackIosIcon from '#material-ui/icons/ArrowBackIos';
import ArrowForwardIosIcon from '#material-ui/icons/ArrowForwardIos';
export function TableSlider({ onClickToRigth, onClickToLeft }) {
return (
<div style={{ zIndex: 1000, top: "39px", position: "absolute", width: "100%", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<ArrowBackIosIcon
onClick={onClickToLeft}
style={{ marginLeft: "4px", cursor: "pointer", opacity: "0.7" }}
/>
<ArrowForwardIosIcon
onClick={onClickToRigth}
style={{ cursor: "pointer", opacity: "0.8" }}
/>
</div>
)
}
My custom material table. Notice that I created a useRef and added it in the material table by doing: tableRef={tableEl}
import React, { useRef } from 'react';
import { Grid } from '#material-ui/core';
import MaterialTable from 'material-table';
import PropTypes from "prop-types";
import { MTableToolbar } from 'material-table';
import { injectIntl } from 'react-intl';
import { TableSlider } from '../table-slider';
function MyMaterialTable(props) {
const tableEl = useRef(null);
const { title, urledit, columns, data, options, deleteItem, rowClick, action } = props;
var opt = options;
if (opt == null) {
opt = {
sorting: true,
pageSize: 10,
showSelectAllCheckbox: true,
columnsButton: true,
toolbar: true,
minBodyHeight: 550,
paging: false,
headerStyle: {
backgroundColor: '#f9f9f9',
fontWeight: 'bold',
borderBottom: '2px solid #b3b3b3',
height: '100px'
}
};
}
opt.toolbar = false;
const handleDelete = (data, resolve) => {
deleteItem(data, resolve);
};
const scrollToRigth = () => {
console.log(tableEl.current)
// Can i use those functions with a useRef?
tableEl.current.scrollTo()
tableEl.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
};
const scrollToLeft = () => {
// Can i use those functions with a useRef?
tableEl.current.scrollTo()
tableEl.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
};
const intl = props.intl;
return (
<Grid container spacing={0} >
<Grid item xs={12} sm={12} style={{ position: "relative", display: 'table', tableLayout: 'fixed', width: '100%', minWidth: '210px', paddingLeft: 0, paddingRight: 0 }}>
<TableSlider onClickToLeft={scrollToLeft} onClickToRigth={scrollToRigth} />
<MaterialTable
title={title}
tableRef={tableEl}
id="material-table-id"
columns={columns}
data={data}
options={opt}
onRowClick={rowClick}
actions={action}
components={{
Toolbar: props => (
<div style={{ backgroundColor: '#e8eaf5', color: '#000000' }}>
<MTableToolbar {...props} />
</div>
),
}}
localization={{
grouping: {
groupedBy: intl.formatMessage({ id: "grouped.by" }),
placeholder: intl.formatMessage({ id: "headers.drap" })
},
pagination: {
labelDisplayedRows: '{from}-{to} ' + intl.formatMessage({ id: "of" }) + ' {count}',
labelRowsPerPage: intl.formatMessage({ id: "recordsPerPage" }),
labelRowsSelect: intl.formatMessage({ id: "records" })
},
toolbar: {
addRemoveColumns: intl.formatMessage({ id: "add.remove" }),
nRowsSelected: '{0}' + intl.formatMessage({ id: "rows.select" }),
showColumnsTitle: intl.formatMessage({ id: "show.columns" }),
showColumnsAriaLabel: intl.formatMessage({ id: "show.columns" }),
exportTitle: intl.formatMessage({ id: "export" }),
exportAriaLabel: intl.formatMessage({ id: "export" }),
exportName: intl.formatMessage({ id: "export.csv" }),
searchTooltip: intl.formatMessage({ id: "search" }),
searchPlaceholder: intl.formatMessage({ id: "search" })
},
header: {
actions: ''
},
body: {
emptyDataSourceMessage: intl.formatMessage({ id: "rows.show" }),
filterRow: {},
editRow: {
saveTooltip: intl.formatMessage({ id: "save" }),
cancelTooltip: intl.formatMessage({ id: "cancel" }),
deleteText: intl.formatMessage({ id: "sure.delete" })
},
addTooltip: intl.formatMessage({ id: "add" }),
deleteTooltip: intl.formatMessage({ id: "delete" }),
editTooltip: intl.formatMessage({ id: "update" })
}
}}
/>
</Grid>
</Grid>
);
}
export default injectIntl(MyMaterialTable);
MyMaterialTable.propTypes = {
title: PropTypes.string,
urledit: PropTypes.string,
columns: PropTypes.array,
data: PropTypes.array,
options: PropTypes.object,
deleteItem: PropTypes.func,
rowClick: PropTypes.func,
action: PropTypes.func
};
Here is my problem, I don't know how to use the ref to scroll my table horizontally. tableEl.current.scrollTo() and tableEl.current.scrollIntoView() returns undefined
const scrollToRigth = () => {
console.log(tableEl.current)
// Can i use those functions with a useRef?
tableEl.current.scrollTo()
tableEl.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
};
const scrollToLeft = () => {
// Can i use those functions with a useRef?
tableEl.current.scrollTo()
tableEl.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
};

react-spring clean scale and translateX transition

The react-swipeable-views library is providing example usages. I want to reuse the coverflow example, but in a react functional comp. way. I almost managed to get it working. However on my implementation, the swipeable element can get stuck during swipe, if you swipe slowly (the scale is not applied anymore). See screenshot:
In the demo of react-swipeable views this is somehow not happening. This example is using react-spring for the animation transitions. I provided a stackblitz demo that is reproducable and maybe you can find out the issue.
component
const useStyles = makeStyles((theme) => ({
root: {
background: theme.palette.background.paper,
padding: theme.spacing(0, 6),
},
img: {
width: 180,
height: 180,
display: "block",
marginBottom: theme.spacing(2),
},
container: {
padding: theme.spacing(2),
borderRadius: 4,
justifyContent: "center",
maxWidth: 320,
margin: "auto",
},
slide: {
padding: theme.spacing(3, 2),
color: theme.palette.text.primary,
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
display: "flex",
},
}));
const albums = [
{
name: "Abbey Road",
src: "https://picsum.photos/200/300",
},
{
name: "Bat Out of Hell",
src: "https://picsum.photos/200/300",
},
{
name: "Homogenic",
src: "https://picsum.photos/200/300",
},
{
name: "Number of the Beast",
src: "https://picsum.photos/200/300",
},
{
name: "It's Blitz",
src: "https://picsum.photos/200/300",
},
{
name: "The Man-Machine",
src: "https://picsum.photos/200/300",
},
];
export function StatisticSelector() {
const classes = useStyles();
const [index, setIndex] = useState(0);
const [props, start] = useSpring(() => ({
from: { position: 0 },
}));
function handleChangeIndex(indexNum) {
setIndex(indexNum);
}
function handleSwitch(index, type) {
if (type === "end") {
start({
from: { position: props.position.value },
to: { position: Math.round(index) },
});
return;
}
props.position.setValue(index);
}
function interpolatePositionProps(range, output) {
return props.position.interpolate({
range,
output,
});
}
return (
<div className={classes.container}>
<SwipeableViews
index={index}
className={classes.root}
onChangeIndex={handleChangeIndex}
onSwitching={handleSwitch}
enableMouseEvents
>
{albums.map((album, currentIndex) => {
const inputRange = albums.map((_, i) => i);
const scale = interpolatePositionProps(
inputRange,
inputRange.map((i) => (currentIndex === i ? 1 : 0.7))
).interpolate((x) => `scale(${x})`);
const opacity = interpolatePositionProps(
inputRange,
inputRange.map((i) => (currentIndex === i ? 1 : 0.3))
);
const translateX = interpolatePositionProps(
inputRange,
inputRange.map((i) => (100 / 2) * (i - currentIndex))
).interpolate((x) => `translateX(${x}px)`);
const scaleAndTranslateX = interpolate(
[scale, translateX],
(scale, translateX) => `${scale} ${translateX}`
);
return (
<animated.div
key={String(currentIndex)}
className={classes.slide}
style={Object.assign({
opacity,
transform: scaleAndTranslateX,
})}
>
<img className={classes.img} src={album.src} alt="cover" />
<Button variant="contained" color="primary" size="small">
Select
</Button>
</animated.div>
);
})}
</SwipeableViews>
</div>
);
}
function handleSwitch(index, type) {
if (type === "end") {
start({
from: { position: props.position.value },
to: { position: Math.round(index) }
});
/**
* Solution:
* Do not stop executing this function and make final value update.
* Just comment `return` below, and everything will work as expected.
*/
// return;
}
props.position.setValue(index);
}

React js chat when scrolling up load old messages

I am developing a chat, as can be seen from the image.
When the chat opens, the chat scrolls down to show the latest messages.
What I would like to do, that when user scrolls up and gets to the last message (i.e. the oldest one in the chat), the oldMessage function is called which makes an http call passing the current page to try to retrieve the previous messages to the last displayed at the top.
I don't know if I've made myself clear.
Can you give me a hand?
Link: codesandbox
import React, { useState, useRef, useEffect } from "react";
import { makeStyles } from "#material-ui/core/styles";
import {
Card,
Typography,
Icon,
useTheme,
TextField,
IconButton,
Avatar,
Paper
} from "#material-ui/core";
import Moment from "react-moment";
import clsx from "clsx";
import moment from "moment/moment";
const message = [
{
id: 1,
createdAt: "",
message: "Hi, James!",
senderId: {
_id: 2,
name: "Vesper",
surname: "Lynd"
}
},
{
id: 2,
createdAt: "",
message: "Hi, Vesper!",
senderId: {
_id: 1,
name: "James",
surname: "Bond"
}
},
{
id: 3,
createdAt: "",
message: "Quickly come to the meeting room 1B, we have a big server issue",
senderId: {
_id: 2,
name: "Vesper",
surname: "Lynd"
}
},
{
id: 4,
createdAt: "",
message: "I’m having breakfast right now, can’t you wait for 10 minutes?",
senderId: {
_id: 1,
name: "James",
surname: "Bond"
}
},
{
id: 5,
createdAt: "",
message: "I’m having breakfast right now, can’t you wait for 10 minutes?",
senderId: {
_id: 1,
name: "James",
surname: "Bond"
}
},
{
id: 6,
createdAt: "",
message: "We are losing money! Quick!",
senderId: {
_id: 2,
name: "Vesper",
surname: "Lynd"
}
},
{
id: 7,
createdAt: "",
message:
"It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.",
senderId: {
_id: 1,
name: "James",
surname: "Bond"
}
},
{
id: 8,
createdAt: "",
message: "You are the worst!",
senderId: {
_id: 2,
name: "Vesper",
surname: "Lynd"
}
},
{
id: 9,
createdAt: "",
message: "We are losing money! Quick!",
senderId: {
_id: 2,
name: "Vesper",
surname: "Lynd"
}
},
{
id: 10,
createdAt: "",
message: "You are the worst!",
senderId: {
_id: 2,
name: "Vesper",
surname: "Lynd"
}
},
{
id: 11,
createdAt: "",
message: "We are losing money! Quick!",
senderId: {
_id: 2,
name: "Vesper",
surname: "Lynd"
}
},
{
id: 12,
createdAt: "",
message:
"It’s not my money, you know. I will eat my breakfast and then I will come to the meeting room.",
senderId: {
_id: 1,
name: "James",
surname: "Bond"
}
}
];
const useStyles = makeStyles((theme) => ({
root: {
"& > *": {
margin: theme.spacing(1)
}
},
messageRow: {
position: "relative",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "flex-end",
padding: "0 16px 4px 16px",
flex: "0 0 auto",
"&.contact": {
"& $bubble": {
backgroundColor: theme.palette.background.paper,
color: theme.palette.getContrastText(theme.palette.background.paper),
borderTopLeftRadius: 5,
borderBottomLeftRadius: 5,
borderTopRightRadius: 20,
borderBottomRightRadius: 20,
marginLeft: 28,
"& $time": {
marginLeft: 12
}
},
"&.first-of-group": {
"& $bubble": {
borderTopLeftRadius: 20
}
},
"&.last-of-group": {
"& $bubble": {
borderBottomLeftRadius: 20
}
}
},
"&.me": {
paddingLeft: 40,
"& $avatar": {
order: 2,
margin: "0 0 0 16px"
},
"& $bubble": {
marginLeft: "auto",
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
borderTopLeftRadius: 20,
borderBottomLeftRadius: 20,
borderTopRightRadius: 5,
borderBottomRightRadius: 5,
"& $time": {
justifyContent: "flex-end",
right: 0,
marginRight: 12
}
},
"&.first-of-group": {
"& $bubble": {
borderTopRightRadius: 20
}
},
"&.last-of-group": {
"& $bubble": {
borderBottomRightRadius: 20
}
}
},
"&.contact + .me, &.me + .contact": {
paddingTop: 20,
marginTop: 20
},
"&.first-of-group": {
"& $bubble": {
borderTopLeftRadius: 20,
paddingTop: 13
}
},
"&.last-of-group": {
"& $bubble": {
borderBottomLeftRadius: 20,
paddingBottom: 13,
"& $time": {
display: "flex"
}
}
}
},
avatar: {
position: "absolute",
left: 0,
margin: 0
},
bubble: {
position: "relative",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: 12,
maxWidth: "100%",
boxShadow: theme.shadows[1]
},
message: {
whiteSpace: "pre-wrap",
lineHeight: 1.2
},
time: {
position: "absolute",
display: "none",
width: "100%",
fontSize: 11,
marginTop: 8,
top: "100%",
left: 0,
whiteSpace: "nowrap"
},
bottom: {
// background: theme.palette.background.default,
// borderTop: '1px solid rgba(0, 0, 0, 0.13)'
},
inputWrapper: {
borderRadius: 24
}
}));
export default function App() {
const classes = useStyles();
const [state, setState] = useState({
userMyInfo: {
id: 1,
name: "James",
surname: "Bond"
},
chat: message,
msgState: "",
pag: 0
});
const { userMyInfo, chat, msgState } = state;
const sendMessage = () => {};
const oldMessage = () => {
//http request
fetch("")
.then((response) => response.json())
.then((message) => {
setState(...(prev) => ({ ...prev, chat: [...message, ...prev.chat] }));
})
.catch((error) => {
console.error(error);
});
};
const messagesEndRef = useRef(null);
const scrollToBottom = () => {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
};
useEffect(scrollToBottom, []);
const shouldShowContactAvatar = (item, i) => {
return (
(chat[i + 1] && chat[i].senderId._id !== chat[i + 1].senderId._id) ||
!chat[i + 1]
);
};
const isFirstMessageOfGroup = (item, i) => {
return (
i === 0 || (chat[i - 1] && chat[i - 1].senderId._id !== item.senderId._id)
);
};
const isLastMessageOfGroup = (item, i) => {
return (
i === chat.length - 1 ||
(chat[i + 1] && chat[i + 1].senderId._id !== item.senderId._id)
);
};
return (
<Paper
elevation={3}
className={clsx(classes.root, "flex flex-col relative pb-64")}
>
<Card elevation={1} className="flex flex-col h-512 rounded-8">
<div
className="flex flex-shrink-0 items-center justify-between px-24 h-64"
style={{
background: "#607d8b"
//color: theme.palette.getContrastText('#607d8b')
}}
>
<Typography className="text-center text-16 font-400">Chat</Typography>
</div>
<div style={{ flex: 1, overflowY: "auto" }}>
{state.chat.length === 0 ? (
<div style={{ textAlign: "center" }}>
Al momento non ci sono messaggi
</div>
) : (
state.chat.map((item, key) => (
<div
key={key}
className={clsx(
classes.messageRow,
{ me: item.senderId._id === userMyInfo.id },
{ contact: item.senderId._id !== userMyInfo.id },
{ "first-of-group": isFirstMessageOfGroup(item, key) },
{ "last-of-group": isLastMessageOfGroup(item, key) }
)}
>
{item.senderId._id !== userMyInfo.id &&
shouldShowContactAvatar(item, key) && (
<Avatar className={classes.avatar}>
{item.senderId.name[0]} {item.senderId.surname[0]}
</Avatar>
)}
<div className={classes.bubble}>
<div className={classes.message}>{item.message}</div>
<Typography className={classes.time} color="textSecondary">
{moment(item.time).format("MMMM Do YYYY, h:mm:ss a")}
</Typography>
</div>
</div>
))
)}
<div ref={messagesEndRef} />
</div>
<div style={{ padding: 5, display: "flex", flexDirection: "row" }}>
<TextField
required
id="outlined-required"
label="Message"
//inputRef={textInput}
placeholder="Message"
//onChange={handleChange}
variant="outlined"
fullWidth
/>
<IconButton onClick={() => sendMessage()} disabled={msgState === ""}>
<Icon>send</Icon>
</IconButton>
</div>
</Card>
</Paper>
);
}
You need to add an event handler for scrolling and check you are at the top of the container
const handleScroll = e => {
let element = e.target;
if (element.scrollTop===0) {
//fetch messages
}
}
<div style={{ flex: 1, overflowY: "auto"}} onScroll={ handleScroll}>
Two things you can do is
Monitor for scroll event on an element using onScroll from react
<ScrollableComponent onScroll={this.handleScroll} />
use the windows scroll event handler and detect when user is at top of page
useEffect(() => {
window.addEventListener('scroll', this.handleScroll);
return () => window.removeEventListener('scroll', this.handleScroll);
}
const handleScroll = (event) => {
// code here
}

Categories