adding more than 1 number value to payload - javascript

I am playing around with a redux tutorial code, I am trying to add another number value to createPolicy action creator, it is showing some error, please tell me what am I missing here
console.clear()
const createPolicy = (name, amount) => {
return {
type : 'CREATEPOLICY',
payload : {
name, amount, bill
}
}
}
const deletePolicy = (name) => {
return {
type : 'DELETEPOLICY',
payload : {
name
}
}
}
const claimRequest = (name, amountofclaim, fees) => {
return {
type : 'CLAIMREQUEST',
payload : {
name, amountofclaim, fees
}
}
}
const creteReducer = (previesdelte = [], action) => {
if(action.type === "CREATEPOLICY") {return [...previesdelte, action.payload ]}
else{return previesdelte}
}
const clamReducer = (totalamount=200, action) => {
if(action.type === "CLAIMREQUEST") {
return totalamount - (action.payload.amountofclaim + action.payload.fees)}
else if(action.type === "CREATEPOLICY") {
return totalamount + (action.payload.amount + action.payload.bill)}
else{return totalamount}
}
const deleteReducer = (userlist = [], action) => {
if(action.type === "CREATEPOLICY"){
return [...userlist, action.payload]
}
else if(action.type === "DELETEPOLICY") {
return userlist.filter( name => name !== action.payload.name)
}
else {
return userlist
}
}
const {createStore, combineReducers} = Redux;
const allReducers = combineReducers({
creteReducer:creteReducer,
clamReducer:clamReducer,
deleteReducer:deleteReducer
})
const store = createStore(allReducers)
store.dispatch(createPolicy('fff', 20, 3))
store.dispatch(claimRequest('fff', 150, 5))
console.log(store.getState())

A parameter bill is probably missing from your createPolicy function.

Related

Redux+Reactjs+NodeJS How to get a specific value from the database and display it to the screen by redux?

i am new, and am getting acquainted with i reactjs and nodejs, i am writing a website in which i want to write a function to get the current login information of the user through redux, and then after then assign the obtained user value to react-select, then from react-select, we select that user to perform the assignment of new data to the database from reactjs. However, I have not been able to get the logged in user information through redux. This is my code, anyone have any ideas? Thanks very much
here is my FE(Reactjs code):
here is DoctorManageSchedule.js:
import React, { Component } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import './ManageSchedule.scss';
import Select from 'react-select';
import * as actions from "../../../store/actions";
import { CRUD_ACTIONS, LANGUAGES, dateFormat } from '../../../utils';
import DatePicker from '../../../components/Input/DatePicker';
import moment from 'moment';
import { toast } from "react-toastify";
import _ from 'lodash';
import { saveBulkScheduleDoctor, getScheduleDoctorById } from '../../../services/userService';
import DetailDoctor from './DetailDoctor';
class DoctorManageSchedule extends Component {
constructor(props) {
super(props);
this.state = {
arrDoctor: [],
selectedDoctor: {},
currentDate: '',
rangeTime: [],
minDate: moment().calendar(),
}
}
async componentDidMount() {
this.props.fetchDoctor(this.props.match.params.id);
this.props.fetchAllScheduleTimes();
}
async componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.doctor !== this.props.doctor) {
let dataSelect = this.buildDataInputSelect(this.props.doctor)
this.setState({
arrDoctor: dataSelect
})
}
if (prevProps.allScheduleTime !== this.props.allScheduleTime) {
let data = this.props.allScheduleTime;
if (data && data.length > 0) {
data = data.map(item => ({ ...item, isSelected: false }))
}
this.setState({
rangeTime: data
})
}
}
buildDataInputSelect = (inputData) => {
let result = [];
let { language } = this.props;
if (inputData && inputData.length > 0) {
inputData.map((item, index) => {
let object = {};
let labelEn = `${item.lastName} ${item.firstName}`;
let labelVi = `${item.firstName} ${item.lastName}`;
object.label = language === LANGUAGES.VI ? labelVi : labelEn;
object.value = item.id;
result.push(object)
})
}
return result;
}
handleChangeSelect = async (selectedOption) => {
this.setState({ selectedDoctor: selectedOption });
}
handleOnChangeDatePicker = (date) => {
this.setState({
currentDate: date[0]
})
}
handleClickBtnTime = (time) => {
let { rangeTime } = this.state;
if (rangeTime && rangeTime.length > 0) {
rangeTime = rangeTime.map(item => {
if (item.id === time.id) item.isSelected = !item.isSelected;
return item;
})
this.setState({
rangeTime: rangeTime
})
}
}
handleSaveSchedule = async () => {
let { rangeTime, selectedDoctor, currentDate } = this.state;
let result = [];
if (!currentDate) {
toast.error("Invalid date!");
}
if (selectedDoctor && _.isEmpty(selectedDoctor)) {
toast.error("Invalid selected doctor! ");
console.log('check doctor: ', this.state)
return;
}
let formatedDate = new Date(currentDate).getTime();
if (rangeTime && rangeTime.length > 0) {
let selectedTime = rangeTime.filter(item => item.isSelected === true);
if (selectedTime && selectedTime.length > 0) {
selectedTime.map((schedule, index) => {
let object = {};
object.doctorId = selectedDoctor.value;
object.date = formatedDate;
object.timeType = schedule.keyMap;
result.push(object);
})
} else {
toast.error("Invalid selected time! ");
return;
}
}
let res = await saveBulkScheduleDoctor({
arrSchedule: result,
doctorId: selectedDoctor.value,
formatedDate: formatedDate
})
if (res && res.errCode === 0) {
toast.success("Save Infor succeed!");
} else {
toast.error("error saveBulkScheduleDoctor ");
console.log('error saveBulkScheduleDoctor >>> res: ', res)
}
console.log('bao phuc check result: ', result);
console.log('check res: saveBulkScheduleDoctor : ', res);
}
render() {
let { rangeTime, arrDoctor } = this.state;
console.log("check doctor:", arrDoctor)
let { language } = this.props;
let today = new Date(new Date().setDate(new Date().getDate()));
return (
<div className="manage-schedule-container">
<div className="m-s-title">
<FormattedMessage id="manage-schedule.title"></FormattedMessage>
</div>
<div className="container">
<div className="row">
<div className="col-6 form-group">
<label>
<FormattedMessage id="manage-schedule.choose-doctor" /> </label>
<Select
value={this.state.selectedDoctor}
onChange={this.handleChangeSelect}
options={this.state.listDoctors}
/>
</div>
<div className="col-6 form-group">
<label>
<FormattedMessage id="manage-schedule.choose-date" /> </label>
<DatePicker
value={this.state.currentDate}
className="form-control"
onChange={this.handleOnChangeDatePicker}
minDate={today}
/>
</div>
<div className="col-12 pick-hour-container">
{rangeTime && rangeTime.length > 0 &&
rangeTime.map((item, index) => {
return (
<button className={item.isSelected === true ?
"btn btn-schedule active" : "btn btn-schedule"}
key={index} onClick={() => this.handleClickBtnTime(item)}>
{language === LANGUAGES.VI ? item.valueVi : item.valueEn}
</button>
)
})}
</div>
<div className="col-12">
<button className="btn btn-primary btn-save-schedule"
onClick={() => this.handleSaveSchedule()}>
<FormattedMessage id="manage-schedule.save" />
</button>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
language: state.app.language,
isLoggedIn: state.user.isLoggedIn,
doctor: state.admin.doctor,
allScheduleTime: state.admin.allScheduleTime,
};
};
const mapDispatchToProps = dispatch => {
return {
fetchDoctor: () => dispatch(actions.fetchDoctorStart()),
fetchAllScheduleTimes: () => dispatch(actions.fetchAllScheduleTimes())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(DoctorManageSchedule);
and here is my actionTypes.js:
const actionTypes = Object.freeze({
FETCH_DOCTOR_SUCCESS: 'FETCH_DOCTOR_SUCCESS',
FETCH_DOCTOR_FAILED: 'FETCH_DOCTOR_FAILED',
})
export default actionTypes;
and here is my adminActions.js:
export const fetchDoctorStart = id => () => {
return async (dispatch, getState) => {
try {
let res = await getScheduleDoctorById(id);
if (res && res.errCode === 0) {
dispatch({
type: actionTypes.FETCH_DOCTOR_SUCCESS,
dataDoctor: res.data
})
} else {
toast.error("Failed to fetch doctor");
dispatch(fetchDoctorFailed());
}
} catch (e) {
toast.error("Failed to fetch doctor");
dispatch(fetchDoctorFailed());
console.log("check fetch doctor failed: ", e);
}
}
};
export const fetchDoctorSuccess = (data) => ({
type: actionTypes.FETCH_DOCTOR_SUCCESS,
doctor: data
})
export const fetchDoctorFailed = () => ({
type: actionTypes.FETCH_DOCTOR_FAILED,
})
here is my adminReducer.js:
case actionTypes.FETCH_DOCTOR_SUCCESS:
state.doctor = action.dataDoctor;
return {
...state,
}
case actionTypes.FETCH_DOCTOR_FAILED:
state.doctor = [];
return {
...state,
}
here is my userService.js:
const getScheduleDoctorById = (inputId) => {
return axios.get(`/api/get-schedule-doctor-by-id?id=${inputId}`)
}
here is my BE(Nodejs code):
here is web.js:
router.get('/api/get-schedule-doctor-by-id', doctorController.getScheduleById);
here is doctorController.js:
let getScheduleById= async (req, res) => {
try {
let infor = await doctorService.getScheduleById(req.query.id);
return res.status(200).json(infor);
} catch (e) {
console.log(e);
return res.status(200).json({
errCode: -1,
errMessage: 'Error from the server'
})
}
}
here is doctorService.js:
let getScheduleById = (inputId) => {
return new Promise(async (resolve, reject) => {
try {
if (!inputId) {
resolve({
errCode: 1,
errMessage: 'Missing required parameter!'
})
} else {
let data = await db.User.findOne({
where: {
id: inputId
},
attributes: {
exclude: ['password']
},
include: [
{ model: db.Allcode, as: 'positionData', attributes: ['valueEn', 'valueVi'] },
{
model: db.Doctor_Infor,
attributes: {
exclude: ['id', 'doctorId']
}
},
],
raw: false,
nest: true
})
if (data && data.image) {
data.image = new Buffer(data.image, 'base64').toString('binary');
}
if (!data) data = {};
resolve({
errCode: 0,
data: data
})
}
} catch (e) {
reject(e);
}
})
}
when i run the app i only get the available time slots from the database, but no info about who is logged in, i checked the network tab, but it seems the api gets the user info via redux do not run. Or does anyone have a way to do it other than using redux, that the user (doctor) can set his own schedule and save it to the database? However, I don't know why, please comment, thanks a lot

How to remove items from the Cart?

I'm new to the JavaScript and I face a problem. I don't understand how to create an event which will remove items from the Cart page. I created addCartEvent but I don't know how to remove products. I already created minus button component. In project I'm using Firebase.
Here's a code from the Cart page:
export default function CartPage() {
const { user, loading } = useAuth();
const { data } = useCart();
const cartLength = Object.keys(data).reduce((a, b) => a + data[b].length, 0);
const cartItems =
cartLength > 0
? Object.keys(data)
.map((item) => {
return data[item].map((size) => {
return {
name: item,
size,
};
});
})
.flat(1)
: [];
const sizeCount = cartItems.reduce(
(acc, value) => ({
...acc,
[value.name + '__size__' + value.size]:
(acc[value.name + '__size__' + value.size] || 0) + 1,
}),
{}
);
const cartItemsArray = [
...new Set(
cartItems.filter(
(v, i, a) =>
a.findIndex((t) => t.name === v.name && t.size === v.size) === i
)
),
].map((item) => {
return { ...item, count: sizeCount[item.name + '__size__' + item.size] };
});
const addCartEvent = (id, size) => {
const newCart = size
? {
...data,
[id]: data.hasOwnProperty(id) ? [...data[id], size] : [size],
}
: {
...data,
[id]: data.hasOwnProperty(id) ? [...data[id], '-'] : ['-'],
};
addToCart(newCart);
};
const router = useRouter();
}
And code from the firebase/product.js:
import { auth, db } from '../config/firebase';
function addToCart(newCart) {
const currentUser = auth.currentUser.uid;
return db.collection('Users').doc(currentUser).update({
cart: newCart,
});
}
export { addToCart };

How to create 'user typing' in socket.io with react?

These days I create advanced chat app. But I cant solve this problem now. How Can I do 'user typing' in socket.io and react.
This is my page when I write message:
import {Input, Button } from 'antd'
import { useConversation } from "../../conversation";
import { SendOutlined } from '#ant-design/icons'
import {Auth} from '../../context'
import {useState, useEffect} from 'react'
import {useSocket} from '../../socket'
export default function Bottom(){
const [text, setText] = useState('')
const [ok, setok] = useState(false)
const {sendMessage, currentConversation} = useConversation()
const {user} = Auth()
const socket = useSocket()
useEffect( ()=>{
if(text.length > 0 && !ok ) {
socket.emit('send-typing', {currentConversation, user })
setok(true)
}
if(text.length === 0 && ok){
socket.emit('stop-typing', {currentConversation, user})
setok(false)
}
},[text] )
function handleSubmit() {
const {recipients, ...other} = currentConversation;
sendMessage(
currentConversation.recipients.map(r => r._id),
{recipients: recipients.push(user), ...other},
text
)
setok(false)
socket.emit('stop-emit', {currentConversation, user})
setText('')
}
return (<div className="open_bottom">
<Input.Group compact >
<Input.TextArea placeholder="Write message" value={text} style={{ resize: 'none' }} onChange={(e)=>setText(e.target.value)}
onPressEnter={handleSubmit}
/>
<Button type="primary" onClick={handleSubmit} > <SendOutlined/> </Button>
</Input.Group>
</div>)
}
This is my backend. Its work successfully:
io.on('connection', socket => {
const id = socket.handshake.query.id
socket.join(id)
console.log("user ", id, " connected" );
socket.on('send-message', async ( {recipients, sender , current, text}, callback )=>{
recipients.forEach(recipient => {
socket.broadcast.to(recipient).emit('receive-message', {
current, sender, text
})
})
let err=false;
try {
const nw = new Messages({ about: current._id, sender: sender._id, text, who_deleted:[], readers:[] });
await nw.save();
} catch (e) {
err=true;
}
callback({err: err})
});
socket.on('send-typing', ({currentConversation, user})=>{
console.log("typing ", currentConversation._id);
currentConversation.recipients.forEach( r=>{
socket.broadcast.to( r ).emit('get-typing', { current: currentConversation, writer: user })
})
})
socket.on('stop-typing', ({currentConversation, user})=>{
console.log("stop-typing ", currentConversation._id);
currentConversation.recipients.forEach( r=>{
socket.broadcast.to( r ).emit('dur-typing', { current: currentConversation, writer: user })
})
})
socket.on('disconnect', ()=>{
console.log("user ", id, " disconnect");
})
})
Problem here: socket.on('dur-typing', ...) and socket.on('get-typing', ...) cannot work. Really words I didnt understand why cannot work or cannot get data. I didnt understand where I must use socket.off. Please help fix this problem
import React, {useContext, useEffect, useState, useMemo, useCallback} from 'react'
import {useSocket} from './socket'
import {Auth} from './context'
const ConversationsContext = React.createContext()
export function useConversation(){
return useContext(ConversationsContext)
}
export default function ConversationProvider( {children} ){
const socket = useSocket()
const [conversations, setConversations] = useState([])
const [currentConversation, setCurrentConversation] = useState(null)
const [typing, setTyping] = useState([])
const [msg, setMsg] = useState(false)
const [typ, setTyp] = useState(false)
const [dur, setDur] = useState(false)
const {user} = Auth()
const addMessageToConversation = useCallback(({ current, sender, text}) => {
setMsg(true)
setConversations(prevConversations => {
let madeChange = false
const newMessage = { sender, text }
const newConversations = prevConversations.map(conversation => {
if( conversation._id === current._id ){
madeChange = true
return {
...conversation,
messages: [...conversation.messages, newMessage]
}
}
return conversation
})
if (madeChange) {
return newConversations
} else {
return [
...prevConversations,
{ ...current, messages: [newMessage] }
]
}
})
}, [setConversations])
useEffect(() => {
if (socket == null) return
setMsg(false)
setTyp(false)
setDur(false)
socket.on('receive-message', addMessageToConversation)
socket.on('get-typing', (current, writer)=>{
console.log("get-typing", current, writer);
setTyp(true)
setTyping( prev=>{
let ok = false;
const nw = prev.map( i=>{
if( i.current._id === current._id ){
ok = true;
return {current, writers: [...i.writers, writer]}
}
else return i;
} )
if(ok) return nw;
else return [...prev, {current, writers: [writer]}]
})
})
socket.on('dur-typing', (current, writer)=>{
setDur(true)
console.log("dur-typing", current, writer);
setTyping( prev=>{
const nw = prev.map( i=>{
if( i.current._id === current._id ){
const wr = i.writers.filter( t =>{
return t._id !== writer._id
} ) ;
return {current, writers: wr }
}
else return i;
} )
return nw;
})
})
return () =>{
if(msg) socket.off('receive-message')
// if(typ) socket.off('get-typing')
// if(dur) socket.off('dur-typing')
}
}, [socket, addMessageToConversation, typing])
console.log(typing);
// useEffect(()=>{
// if(currentConversation === null || socket==null ) return
// socket.emit('read-message', {_id: currentConversation._id, user: user._id}, ({err})=>{
// if(err) alert( "Cannot update!" );
// })
// }, [socket, currentConversation ])
function sendMessage(recipients, current, text) {
socket.emit('send-message', { recipients, sender: user, current, text }, ({err})=>{
if(err) alert(" Try again! ")
});
addMessageToConversation({ current, sender:user , text })
}
function rnd_arr(arr) {
var currentIndex = arr.length, randomIndex;
while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[arr[currentIndex], arr[randomIndex]] = [arr[randomIndex], arr[currentIndex]];
}
return arr;
}
const formatted = useMemo ( ()=> conversations.map( i=>{
const { recipients, groupName, ...other } = i;
const all = recipients.filter( t=>{
return t._id !== user._id
})
const logos = rnd_arr(all)
let l = [], name = ""
l.push( logos[0].logo )
name = logos[0].username
if( logos.length > 1 ){
l.push(logos[1].logo)
if( groupName.length > 0 ) name = groupName
else name += ',...'
}
return {recipients: all, name, logo: l, isGroup: all.length > 1,groupName, isAdmin: i.admins.includes(user._id), ...other }
} ), [conversations])
const t = useMemo( ()=>{
if( currentConversation === null ) return null;
else return formatted[formatted.findIndex( t => {
return ( t._id === currentConversation._id );
} )]
}, [formatted,currentConversation] )
const value = {
setConversations,
conversations: formatted,
setCurrentConversation,
currentConversation: t,
sendMessage,
typing
}
return (
<ConversationsContext.Provider value={value}>
{ children }
</ConversationsContext.Provider>
)
}

React infinite scroll component performance

I have written the following infinite scroll component in React:
import React from 'react'
import { uniqueId, isUndefined, hasVerticalScrollbar, hasHorizontalScrollbar, isInt, throttle } from '../../../js/utils';
export default class BlSimpleInfiniteScroll extends React.Component {
constructor(props) {
super(props)
this.handleScroll = this.handleScroll.bind(this)
this.itemsIdsRefsMap = {}
this.isLoading = false
this.node = React.createRef()
}
componentDidMount() {
const {
initialId
} = this.props
let id
if (initialId) {
if (typeof initialId === "function") {
id = initialId()
}
else {
id = initialId
}
this.scrollToId(id)
}
}
componentDidUpdate(prevProps) {
if (
this.isLoading
&&
prevProps.isInfiniteLoading
&&
!this.props.isInfiniteLoading
) {
const axis = this.axis()
const scrollProperty = this.scrollProperty(axis)
const offsetProperty = this.offsetProperty(axis)
this.scrollTo(scrollProperty, this.node.current[offsetProperty])
this.isLoading = false
}
}
itemsRenderer(items) {
const length = items.length
let i = 0
const renderedItems = []
for (const item of items) {
renderedItems[i] = this.itemRenderer(item.id, i, length)
i++
}
return renderedItems
}
itemRenderer(id, i, length) {
const {
itemRenderer,
isInfiniteLoading,
displayInverse
} = this.props
let renderedItem = itemRenderer(id, i)
if (isInfiniteLoading) {
if (!displayInverse && (i == length - 1)) {
renderedItem = this.standardLoadingComponentWrapperRenderer(id, renderedItem)
}
else if (i == 0) {
renderedItem = this.inverseLoadingComponentWrapperRenderer(id, renderedItem)
}
}
const ref = this.itemsIdsRefsMap[id] || (this.itemsIdsRefsMap[id] = React.createRef())
return (
<div className="bl-simple-infinite-scroll-item"
key={id}
ref={ref}>
{renderedItem}
</div>
)
}
loadingComponentRenderer() {
const {
loadingComponent
} = this.props
return (
<div className="bl-simple-infinite-scroll-loading-component"
key={uniqueId()}>
{loadingComponent}
</div>
)
}
loadingComponentWrapperRenderer(id, children) {
return (
<div className="bl-simple-infinite-scroll-loading-component-wrapper"
key={id}>
{children}
</div>
)
}
standardLoadingComponentWrapperRenderer(id, renderedItem) {
return this.loadingComponentWrapperRenderer(id, [
renderedItem,
this.loadingComponentRenderer()
])
}
inverseLoadingComponentWrapperRenderer(id, renderedItem) {
return this.loadingComponentWrapperRenderer(id, [
this.loadingComponentRenderer(),
renderedItem
])
}
axis() {
return this.props.axis === 'x' ? 'x' : 'y'
}
scrollProperty(axis) {
return axis == 'y' ? 'scrollTop' : 'scrollLeft'
}
offsetProperty(axis) {
return axis == 'y' ? 'offsetHeight' : 'offsetWidth'
}
scrollDimProperty(axis) {
return axis == 'y' ? 'scrollHeight' : 'scrollWidth'
}
hasScrollbarFunction(axis) {
return axis == 'y' ? hasVerticalScrollbar : hasHorizontalScrollbar
}
scrollToStart() {
const axis = this.axis()
this.scrollTo(
this.scrollProperty(axis),
!this.props.displayInverse ?
0
:
this.scrollDimProperty(axis)
)
}
scrollToEnd() {
const axis = this.axis()
this.scrollTo(
this.scrollProperty(axis),
!this.props.displayInverse ?
this.scrollDimProperty(axis)
:
0
)
}
scrollTo(scrollProperty, scrollPositionOrPropertyOfScrollable) {
const scrollableContentNode = this.node.current
if (scrollableContentNode) {
scrollableContentNode[scrollProperty] = isInt(scrollPositionOrPropertyOfScrollable) ?
scrollPositionOrPropertyOfScrollable
:
scrollableContentNode[scrollPositionOrPropertyOfScrollable]
}
}
scrollToId(id) {
if (this.itemsIdsRefsMap[id] && this.itemsIdsRefsMap[id].current) {
this.itemsIdsRefsMap[id].current.scrollIntoView()
}
}
handleScroll() {
const {
isInfiniteLoading,
infiniteLoadBeginEdgeOffset,
displayInverse
} = this.props
if (
this.props.onInfiniteLoad
&&
!isInfiniteLoading
&&
this.node.current
&&
!this.isLoading
) {
const axis = this.axis()
const scrollableContentNode = this.node.current
const scrollProperty = this.scrollProperty(axis)
const offsetProperty = this.offsetProperty(axis)
const scrollDimProperty = this.scrollDimProperty(axis)
const currentScroll = scrollableContentNode[scrollProperty]
const currentDim = scrollableContentNode[offsetProperty]
const scrollDim = scrollableContentNode[scrollDimProperty]
const finalInfiniteLoadBeginEdgeOffset = !isUndefined(infiniteLoadBeginEdgeOffset) ?
infiniteLoadBeginEdgeOffset
:
currentDim / 2
let thresoldWasReached = false
let memorizeLastElementBeforeInfiniteLoad = () => { }
if (!displayInverse) {
thresoldWasReached = currentScroll >= (scrollDim - finalInfiniteLoadBeginEdgeOffset)
}
else {
memorizeLastElementBeforeInfiniteLoad = () => {
// TODO
}
thresoldWasReached = currentScroll <= finalInfiniteLoadBeginEdgeOffset
}
if (thresoldWasReached) {
this.isLoading = true
memorizeLastElementBeforeInfiniteLoad()
this.props.onInfiniteLoad()
}
}
}
render() {
const {
items
} = this.props
return (
<div className="bl-simple-infinite-scroll"
ref={this.node}
onScroll={this.handleScroll}
onMouseOver={this.props.onInfiniteScrollMouseOver}
onMouseOut={this.props.onInfiniteScrollMouseOut}
onMouseEnter={this.props.onInfiniteScrollMouseEnter}
onMouseLeave={this.props.onInfiniteScrollMouseLeave}>
{this.itemsRenderer(items)}
</div>
)
}
}
And I use it like this in a chat app I am writing:
...
<BlSimpleInfiniteScroll items={chat.messages}
ref={this.infiniteScrollComponentRef}
initialId={() => lastOfArray(chat.messages).id}
itemRenderer={(id, i) => this.messageRenderer(id, i, chat.messages)}
loadingComponent={<BlLoadingSpinnerContainer />}
isInfiniteLoading={isChatLoading}
displayInverse
infiniteLoadBeginEdgeOffset={void 0}
infiniteLoadingBeginBottomOffset={void 0}
onInfiniteLoad={() => this.props.onLoadPreviousChatMessages(chat.id)}
onInfiniteScrollMouseEnter={this.handleInfiniteScrollMouseEnter}
onInfiniteScrollMouseLeave={this.handleInfiniteScrollMouseLeave} />
...
The problem is that as soon as I scroll until the thresold and onInfiniteLoad is called, before the loading spinner is showed and after the data has been loaded the scroll freezes and the component becomes unresponsive.
How can I resolve this issue?
When I render the spinner container and after the previous loaded messages, shouldn't React just append the new divs retaining the previously added items in order to maintain the component performant?
If not, what key concepts of React I am missing?
Thank you for your attention!
UPDATE: Here are the additional components:
BlOrderChat represents a chat window and renders BlSimpleInfiniteScroll:
import React from 'react'
import BlOrderChatMessage from './BlOrderChatMessage';
import { isEmpty, uniqueId } from '../../../js/utils';
import { chatSelector } from '../selectors';
import BlLoadingSpinnerContainer from '../../core/animation/loading/BlLoadingSpinnerContainer';
import BlSimpleInfiniteScroll from '../../core/scroll/BlSimpleInfiniteScroll';
export default class BlOrderChat extends React.Component {
static BL_ORDER_CHAT_MESSAGE_ID_ATTR_PREFIX = 'blOrderChatMessage'
constructor(props) {
super(props)
this.messageRenderer = this.messageRenderer.bind(this)
this.infiniteScrollComponentRef = React.createRef()
}
scrollToBottom() {
this.infiniteScrollComponentRef.current && this.infiniteScrollComponentRef.current.scrollToStart()
}
messageRenderer(messageId, index, messages) {
const {
currentUser, chat
} = this.props
const message = messages[index]
const length = messages.length
const fromUser = chat.users.items[message.from_user_id]
const itemComponentRender = (children) => (
<div key={messageId}>
{children}
</div>
)
const messageIdAttr = `${BlOrderChat.BL_ORDER_CHAT_MESSAGE_ID_ATTR_PREFIX}${message.id}`
const renderMessageComponent = () => (
<BlOrderChatMessage id={messageIdAttr}
key={uniqueId() + message.id}
message={message.message}
sentUnixTs={message.sent_unix_ts}
currentUser={currentUser}
fromUser={fromUser}
usersInvolvedInChatLength={chat.users.order.length} />
)
let children = []
if (index === 0) {
// First message.
children = [
<div key={uniqueId()} className="bl-padding"></div>,
renderMessageComponent()
]
}
else if (index === length - 1) {
// Last message.
children = [
renderMessageComponent(onComponentDidMount),
<div key={uniqueId()} className="bl-padding"></div>
]
}
else {
// Message in the middle.
children = [
renderMessageComponent()
]
}
return itemComponentRender(children)
}
render() {
const {
chat: propsChat, isChatLoading,
currentUser
} = this.props
const chat = chatSelector(propsChat, currentUser)
const chatHasMessages = chat && !isEmpty(chat.messages)
return (
<div className="bl-order-chat">
<div className="bl-order-chat-header">
// ...
</div>
<div className="bl-order-chat-content">
{
(chatHasMessages &&
<div className="bl-order-chat-content-inner">
<div className="bl-order-chat-infinite-scroll">
<BlSimpleInfiniteScroll items={chat.messages}
ref={this.infiniteScrollComponentRef}
initialId={() => lastOfArray(chat.messages).id}
itemRenderer={(id, i) => this.messageRenderer(id, i, chat.messages)}
loadingComponent={<BlLoadingSpinnerContainer />}
isInfiniteLoading={isChatLoading}
displayInverse
infiniteLoadBeginEdgeOffset={void 0}
infiniteLoadingBeginBottomOffset={void 0}
onInfiniteLoad={() => this.props.onLoadPreviousChatMessages(chat.id)}
onInfiniteScrollMouseEnter={this.handleInfiniteScrollMouseEnter}
onInfiniteScrollMouseLeave={this.handleInfiniteScrollMouseLeave} />
</div>
</div>
)
||
(isChatLoading &&
<BlLoadingSpinnerContainer />
)
}
</div>
<div className="bl-order-chat-footer">
// ...
</div>
</div>
)
}
}
BlOrderChatBox, contains BlOrderChat:
import React from 'react'
import BlOrderChat from './BlOrderChat';
import BlAlert from '../../core/alert/BlAlert';
import BlLoadingSpinnerContainer from '../../core/animation/loading/BlLoadingSpinnerContainer';
export default class BlOrderChatBox extends React.Component {
constructor(props) {
super(props)
this.node = React.createRef()
}
render() {
const {
ordId, currentChat,
isCurrentChatLoading, currentUser,
err
} = this.props
return (
<div className="bl-order-chat-box" ref={this.node}>
<div className="bl-order-chat-box-inner">
{
(err &&
<BlAlert type="error" message={err} />)
||
(currentChat && (
// ...
<div className="bl-order-chat-box-inner-chat-content">
<BlOrderChat ordId={ordId}
chat={currentChat}
isChatLoading={isCurrentChatLoading}
onLoadPreviousChatMessages={this.props.onLoadPreviousChatMessages}
currentUser={currentUser} />
</div>
))
||
<BlLoadingSpinnerContainer />
}
</div>
</div>
)
}
}
And here is the component which renders BlOrderChatBox (it is the topmost stateful component):
import React from 'react'
import { POSTJSON } from '../../../js/ajax';
import config from '../../../config/config';
import { newEmptyArrayAble, arrayToArrayAbleItemsOrder, arrayAbleItemsOrderToArray, mergeArrayAbles, newArrayAble, firstOfArrayAble, isArrayAble } from '../../../js/data_structures/arrayable';
export default class BlOrderChatApp extends React.Component {
static NEW_CHAT_ID = 0
static MAX_NUMBER_OF_MESSAGES_TO_LOAD_PER_AJAX = 30
constructor(props) {
super(props)
this.currentUser = globals.USER
this.lastHandleSendMessagePromise = Promise.resolve()
this.newMessagesMap = {}
this.typingUsersDebouncedMap = {}
// Imagine this comes from a database.
const chat = {
// ...
}
const initialState = {
chats: newArrayAble(this.newChat(chat)),
currentChatId: null,
shouldSelectUserForNewChat: false,
newChatReceivingUsers: newEmptyArrayAble(),
isChatListLoading: false,
isCurrentChatLoading: false,
popoverIsOpen: false,
popoverHasOpened: false,
err: void 0,
focusSendMessageTextarea: false,
newChatsIdsMap: {},
currentChatAuthActs: {},
BlOrderChatComponent: null,
}
this.state = initialState
this.handleLoadPreviousChatMessages = this.handleLoadPreviousChatMessages.bind(this)
}
POST(jsonData, callback) {
let requestJSONData
if (typeof jsonData === "string") {
requestJSONData = {
action: jsonData
}
}
else {
requestJSONData = jsonData
}
return POSTJSON(config.ORDER_CHAT_ENDPOINT_URI, {
...requestJSONData,
order_chat_type: this.props.orderChatType,
}).then(response => response.json()).then(json => {
this.POSTResponseData(json, callback)
})
}
POSTResponseData(data, callback) {
if (data.err) {
this.setState({
err: data.err
})
}
else {
callback && callback(data)
}
}
newChat(chat) {
const newChat = {
id: (chat && chat.id) || BlOrderChatApp.NEW_CHAT_ID,
ord_id: this.props.ordId,
users: (chat && chat.users && (isArrayAble(chat.users) ? chat.users : arrayToArrayAbleItemsOrder(chat.users))) || newEmptyArrayAble(),
messages: (chat && chat.messages && (isArrayAble(chat.messages) ? chat.messages : arrayToArrayAbleItemsOrder(chat.messages))) || newEmptyArrayAble(),
first_message_id: (chat && chat.first_message_id) || null,
typing_users_ids_map: (chat && chat.typing_users_ids_map) || {},
}
return newChat
}
isChatNew(chat) {
return (
chat
&&
(chat.id == BlOrderChatApp.NEW_CHAT_ID || this.state.newChatsIdsMap[chat.id])
)
}
loadPreviousChatMessages(chatId, lowestMessageIdOrNull, makeChatIdCurrent) {
this.POST({
act: 'loadPreviousChatMessages',
chat_id: chatId,
lowest_message_id: lowestMessageIdOrNull,
max_number_of_messages_to_load: BlOrderChatApp.MAX_NUMBER_OF_MESSAGES_TO_LOAD_PER_AJAX
}, json => {
this.setState(prevState => {
const chat = prevState.chats.items[chatId]
const messages = arrayToArrayAbleItemsOrder(json.messages)
const newChat = {
...chat,
messages: mergeArrayAbles(messages, chat.messages)
}
const chats = mergeArrayAbles(prevState.chats, newArrayAble(newChat))
return {
...(makeChatIdCurrent ?
{
currentChatId: chatId,
focusSendMessageTextarea: true,
}
:
{
currentChatId: prevState.currentChatId,
}
),
chats,
isCurrentChatLoading: false,
}
})
})
}
loadPreviousChatMessagesIfNotAllLoaded(chatId) {
let lowestMessageIdOrNull
const chat = this.state.chats.items[chatId]
if (
!this.isChatNew(chat)
&&
(lowestMessageIdOrNull = (chat.messages.order.length && firstOfArrayAble(chat.messages).id) || null)
&&
lowestMessageIdOrNull != chat.first_message_id
) {
this.setState({
isCurrentChatLoading: true
}, () => {
this.loadPreviousChatMessages(chat.id, lowestMessageIdOrNull)
})
}
}
handleLoadPreviousChatMessages(chatId) {
this.loadPreviousChatMessagesIfNotAllLoaded(chatId)
}
// ...
render() {
const currentChat = this.state.chats.items[this.state.currentChatId] || null
const err = this.state.err
return (
<div className="bl-order-chat-app">
<BlOrderChatBox currentUser={this.currentUser}
chats={arrayAbleItemsOrderToArray(this.state.chats)}
currentChat={currentChat}
isCurrentChatLoading={this.state.isCurrentChatLoading}
onLoadPreviousChatMessages={this.handleLoadPreviousChatMessages}
err={err} />
</div>
)
}
}
I tried to remove all the irrelevant code to simplify the reading. Also here is the file which contains the chatSelector function (normalizes the chat array-able object) and the *ArrayAble* functions (an array-able object to me is basically an object which maps objects through their ids in items and has an order property which keeps the sort):
import { isUndefined, unshiftArray, findIndex } from "../utils";
export function chatSelector(chat, currentUser) {
const newChat = { ...chat }
newChat.messages = arrayAbleItemsOrderToArray(chat.messages).sort((a, b) => {
const sortByUnixTs = a.sent_unix_ts - b.sent_unix_ts
if (sortByUnixTs == 0) {
return a.id - b.id
}
return sortByUnixTs
})
newChat.users = arrayAbleItemsOrderToArray(chat.users).filter(user => user.id != currentUser.id)
return newChat
}
/**
* Given an array-able object, returns its array representation using an order property.
* This function acts as a selector function.
*
* The array-able object MUST have the following shape:
*
* {
* items: {},
* order: []
* }
*
* Where "items" is the object containing the elements of the array mapped by values found in "order"
* in order.
*
* #see https://medium.com/javascript-in-plain-english/https-medium-com-javascript-in-plain-english-why-you-should-use-an-object-not-an-array-for-lists-bee4a1fbc8bd
* #see https://medium.com/#antonytuft/maybe-you-would-do-something-like-this-a1ab7f436808
*
* #param {Object} obj An object.
* #param {Object} obj.items The items of the object mapped by keys.
* #param {Array} obj.order The ordered keys.
* #return {Array} The ordered array representation of the given object.
*/
export function arrayAbleItemsOrderToArray(obj) {
const ret = []
for (const key of obj.order) {
if (!isUndefined(obj.items[key])) {
ret[ret.length] = obj.items[key]
}
}
return ret
}
export function arrayToArrayAbleItemsOrder(array, keyProp = "id") {
const obj = newEmptyArrayAble()
for (const elem of array) {
const key = elem[keyProp]
obj.items[key] = elem
obj.order[obj.order.length] = key
}
return obj
}
export function newEmptyArrayAble() {
return {
items: {},
order: []
}
}
export function isEmptyArrayAble(arrayAbleObj) {
return !arrayAbleObj.order.length
}
export function mergeArrayAbles(arrayAbleObj1, arrayAbleObj2, prependObj2 = false) {
const obj = newEmptyArrayAble()
for (const key of arrayAbleObj1.order) {
if (isUndefined(arrayAbleObj1.items[key])) {
continue
}
obj.items[key] = arrayAbleObj1.items[key]
obj.order[obj.order.length] = key
}
for (const key of arrayAbleObj2.order) {
if (isUndefined(arrayAbleObj2.items[key])) {
continue
}
if (!(key in obj.items)) {
if (!prependObj2) {
// Default.
obj.order[obj.order.length] = key
}
else {
unshiftArray(obj.order, key)
}
}
obj.items[key] = arrayAbleObj2.items[key]
}
return obj
}
export function newArrayAble(initialItem = void 0, keyProp = "id") {
const arrayAble = newEmptyArrayAble()
if (initialItem) {
arrayAble.items[initialItem[keyProp]] = initialItem
arrayAble.order[arrayAble.order.length] = initialItem[keyProp]
}
return arrayAble
}
export function lastOfArrayAble(obj) {
return (
(
obj.order.length
&&
obj.items[obj.order[obj.order.length - 1]]
)
||
void 0
)
}
Thank you for your help. If there's something missing which I should have included, please, let me know!
UPDATE: Thanks to Sultan H. it has improved, though the scroll still blocks as soon as I get the reply from the server. See it here: https://streamable.com/3nzu0
Any idea on how to improve this behaviour further?
Thanks!
Here is an attempt to resolve the performance issue, it's not preferrable to do tasks inside the Arrow Function that calculates the new state, in this case, at loadPreviousChatMessages you are calculating stuff in the callback, which may yeild to a load while setting the state on that context.
Preferrable Changes, replace this.setState inside your function with this code, all I've done here is clear the context by moving all the tasks out:
const chat = this.state.chats.items[chatId];
const messages = arrayToArrayAbleItemsOrder(json.messages);
const newChat = {
...chat,
messages: mergeArrayAbles(messages, chat.messages);
}
const chats = mergeArrayAbles(prevState.chats, newArrayAble(newChat));
const newState = {
...(
makeChatIdCurrent ?
{
currentChatId: chatId,
focusSendMessageTextarea: true,
}
:
{
currentChatId: this.state.currentChatId,
}
),
chats,
isCurrentChatLoading: false,
};
this.setState(() => newState);
If that doesn't entirely solve the issue, can you tell if there was at least an improvment?

Redux increase quantity in a shopping cart

I am struggling with the function of my reducer to add an item and increase its quantity if already present in cart.
What my code does so far is adding another "quantity" with 1 instead of updating the quantity already present in my state.
Here's my code :
reducer :
import { ADD_TO_CART } from "../actions/types";
export default function(state = [], action) {
switch (action.type) {
case ADD_TO_CART:
if (state.findIndex(el => el.item.title === action.item.title) === -1) {
return [...state, { item: action.item, quantity: action.quantity + 1 }];
} else {
return [...state, { quantity: action.quantity + 1 }];
}
default:
return state;
}
}
action :
import { ADD_TO_CART } from "./types";
import axios from "axios";
export const addToCart = id => dispatch => {
axios
.get(`https://api.itbook.store/1.0/search/${id}`)
.then(items =>
items.map((item, quantity) =>
dispatch({
type: ADD_TO_CART,
item,
quantity
})
)
);
};
Thanks
You are finding the index (which is great) but not doing anything with it (which is not so great):
import { ADD_TO_CART } from "../actions/types";
export default function(state = [], action) {
switch (action.type) {
case ADD_TO_CART:
const index = state.findIndex(el => el.item.title === action.item.title);
if (index === -1) {
return [...state, { item: action.item, quantity: action.quantity + 1 }];
} else {
// Use map to create a new state object
return state.map((item, i) =>
index === i //Only modify the found index
? { ...item, quantity: item.quantity + action.quantity } //Add the required quantity to the current quantity (not too sure about this)
: item //Don't modify other items
);
}
default:
return state;
}
}
import { ADD_TO_CART } from "../actions/types";
export default function (state = [], action) {
switch (action.type) {
case ADD_TO_CART:
const index = state.findIndex(el => el.item.title === action.item.title);
if (index > -1) {
const newState = [...state];
newState[index] = { ...newState[index], quantity: action.quantity + 1 };
return newState;
} else {
return [...state, { ...action.item, quantity: action.quantity + 1 }];
}
default:
return state;
}
}

Categories

Resources