Why is the state changing by itself in react? - javascript

What I'm Trying to do: I have the following react component, which is chat box, which toggles to view on clicking a button. The chatBox will be opened or closed according the value in the state 'open'. When the user clicks on the chat button, the function 'toggleChat' is run, which just toggles the value of 'open' state between true and false.
Problem: Now the problem is, when a new message is received, I am trying to keep the count of unread messages, if the chatBox isn't 'opened'. But it fails. In my opinion it should work, but the 'open' state is not what I expect it to be sometimes. Sometimes, even though the chatBox is opened, the open state inside is 'false'.
Minified Code
export default function Chat (props) {
const [open, setOpen] = useState(false);
const [unreadMsgs, setUnreadMsgs] = useState(0);
useEffect(() => {
socket.emit('join', uuid);
socket.on('newMsg', msg => {
setMsgArr(prevMsgArr => [ ...prevMsgArr, { type: 'received', msg }]);
if(!open) setUnreadMsgs(prevCount => prevCount + 1);
});
}, []);
const toggleChat = () => {
if(open) setOpen(false);
else {
setOpen(true);
setUnreadMsgs(0);
}
}
Entire code
import { io } from 'socket.io-client';
import React, {useState, useRef, useEffect} from 'react';
import Menu from '#material-ui/core/Menu';
import MailIcon from '#material-ui/icons/Mail';
import CloseIcon from '#material-ui/icons/Close';
import IconButton from '#material-ui/core/IconButton';
import { makeStyles } from '#material-ui/core/styles';
import Badge from '#material-ui/core/Badge';
import Tooltip from '#material-ui/core/Tooltip';
import FiberManualRecordIcon from '#material-ui/icons/FiberManualRecord';
const socket = io('http://127.1.1.1:4000');
const useStyles = makeStyles({
paper : {
height : 300,
},
list : {
height : '100%',
boxSizing : 'border-box',
},
chatContainer : {
position : 'relative',
height : '95%',
width : 300,
},
chatBox : {
height : '82%',
position : 'absolute',
top : '8%',
width : '100%',
overflowY : 'auto',
},
msgForm : {
width : '100%',
padding : 10,
position : 'absolute',
bottom : 0,
height : '6%',
textAlign : 'center',
},
anchor : {
top : 7,
},
badge : {
background : '#007eff',
},
});
export default function Chat (props) {
const uuid = props.uuid;
const classes = useStyles();
const [open, setOpen] = useState(false);
const [activeStatus, setActiveStatus] = useState('Offline');
const [msgArr, setMsgArr] = useState([]);
const chatBtnRef = useRef();
const chatBoxRef = useRef();
const msgInputRef = useRef();
//working on showing count of unread messages
const [unreadMsgs, setUnreadMsgs] = useState(0);
useEffect(() => {
socket.emit('join', uuid);
socket.on('newMsg', msg => {
setMsgArr(prevMsgArr => [ ...prevMsgArr, { type: 'received', msg }]);
setTimeout(() => {
chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
}, 50);
console.log(open);
if(!open) setUnreadMsgs(prevCount => prevCount + 1);
});
if(props.isReceiver) {
socket.emit('isReceiverOnline', uuid);
socket.on('isSenderOnline', () => {
setActiveStatus('Online');
});
} else {
socket.emit('isSenderOnline', uuid);
socket.on('isReceiverOnline', () => {
setActiveStatus('Online');
socket.emit('isSenderOnline', uuid);
});
}
socket.on("isOffline", () => {
setActiveStatus('Offline');
});
return () => {
socket.off('isOffline');
socket.off('newMsg');
socket.off('isOnline');
}
}, []);
const handleMsgSend = e => {
e.preventDefault();
let msg = msgInputRef.current.value;
setMsgArr([ ...msgArr, { type: 'sent', msg }]);
e.currentTarget.reset();
socket.emit('newMsg', {uuid, msg});
setTimeout(() => chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight, 50);
}
const toggleChat = () => {
if(open) setOpen(false);
else {
setOpen(true);
setUnreadMsgs(0);
}
}
return (
<>
<Tooltip title={ `${activeStatus}` }>
<IconButton>
<FiberManualRecordIcon style={{ height : 14, width : 14, fill : (activeStatus == "Offline")? '#00000057' : 'rgb(136 210 130)'}}/>
</IconButton>
</Tooltip>
<Tooltip title={ `${unreadMsgs} unread messages` }>
<IconButton ref={chatBtnRef} onClick={ toggleChat }>
<MailIcon style={{ fill:'white' }}/>
</IconButton>
</Tooltip>
<Menu
classes={{ paper : classes.paper, list : classes.list}}
anchorEl={chatBtnRef.current}
keepMounted
open={ open }
>
<div style={{ position : 'relative', zIndex : '1', height : '5%', textAlign:'right'}}>
<IconButton onClick={ toggleChat }>
<CloseIcon />
</IconButton>
</div>
<div className={classes.chatContainer}>
<div ref={ chatBoxRef } className={ classes.chatBox }>
{
msgArr.map((msgObj, index) => {
return (
<div key={index} className={`msg-container ${(msgObj.type == 'sent')? 'myMsg' : 'hisMsg'}`}>
<span className='msg'>
{ msgObj.msg }
</span>
</div>
)
})
}
</div>
<form className={ classes.msgForm } onSubmit={ handleMsgSend }>
<input
style ={{
padding : 3,
fontSize : 14,
borderRadius : 3,
width : 250
}}
ref={msgInputRef} type="text"
className={classes.msgInput}
placeholder="Type your Msg here."/>
</form>
</div>
</Menu>
</>
);
}

Try adding the dependencies to useEffect that are used in the function.
Also add const [msgArr, setMsgArr] = useState([]); if it is not there.
useEffect(() => {
socket.emit('join', uuid);
}, []); // you can pass socket here as it's not going to change
useEffect(() => {
socket.on("newMsg", (msg) => {
setMsgArr((prevMsgArr) => [...prevMsgArr, { type: "received", msg }]);
if (!open) setUnreadMsgs((prevCount) => prevCount + 1);
});
return () => socket.removeAllListeners("newMsg"); // remove earlier registered handlers
}, [open]);
Updated the De-registration of the event handler if a dependency changes. (based on comment discussion and answer https://stackoverflow.com/a/67073527/8915198)

Already given the explanation for closures in the comments but the working solution for you should look something like below :-
useEffect(() => {
socket.emit('join', uuid);
}, []);
useEffect(() => {
function getNewMsg(msg){
setMsgArr((prevMsgArr) => [...prevMsgArr, { type: "received", msg }]);
if (!open) setUnreadMsgs((prevCount) => prevCount + 1);
}
socket.on("newMsg",getNewMsg)
return ()=>socket.removeListener("newMsg",getNewMsg);
}, [open]);
Basically the cleanup step I think is necessary as you do with major event paradigms.

Related

(React Native) How to keep animated component in new place after initial animation

I have a <TextInput> which, when the user enters anything into, will have a button appear from behind it.
This works fine using the Animated library from react, but each time I input text into the area, the animation "rubber bands" up and down.
My question is: How can I keep the button in place after the initial animation happens?
I still need to monitor whether the text box is empty in order to decide whether to show or hide the button.
My code:
import { View, TextInput, StyleSheet, Animated, TouchableOpacity } from "react-native";
import { useState } from "react";
import CurrencyInput from 'react-native-currency-input';
import { Ionicons } from "#expo/vector-icons";
type expenseItemData = {
item:string;
costAndSign:string;
cost:number | null;
}
type Props = {
sendItem: ({ item, cost, costAndSign }: expenseItemData) => void;
}
const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);
const AddAndConfirmExpense: React.FC<Props> = ({sendItem}) => {
const [animation] = useState(new Animated.Value(-58));
const [expenseValue, setExpenseValue] = useState<number>(0.00);
const [expenseItem, setExpenseItem] = useState<string>("");
const [expenseValueAndSign, SetExpenseValueAndSign] = useState<string>("");
const [buttonLayoutPersistence, setButtonLayoutPersistence] = useState({
bottom:0
});
const [validExpenseItem, setValidExpenseItem] = useState<boolean>(false);
const onChangeExpenseItem = (text: string) => {
setExpenseItem(text);
if (text.trim().length === 0) {
setValidExpenseItem(false);
hideButton();
setButtonLayoutPersistence({bottom:0})
return;
}
if (validExpenseItem) {
setButtonLayoutPersistence({bottom:-48})
return
};
showButton();
setValidExpenseItem(true);
};
const onButtonPress = () => {
const newData:expenseItemData = {
item:expenseItem,
costAndSign:expenseValueAndSign,
cost:expenseValue
}
sendItem(newData);
};
const setAreaDynamicStyling = () => {
if (validExpenseItem) {
return {
borderTopRightRadius:5,
borderTopLeftRadius:5,
backgroundColor:"#f5f5f5"
}
}
return {borderRadius:5};
};
const setButtonDynamicStyling = () => {
if (!validExpenseItem) return {borderRadius:5}
return {borderBottomLeftRadius: 5,borderBottomRightRadius:5}
};
const animatedStyle = {transform: [{translateY:animation}],};
const showButton = () => {
Animated.timing(animation, {
toValue: -10,
duration: 1000,
useNativeDriver: true,
}).start();
}
const hideButton = () => {
Animated.timing(animation, {
toValue: -58,
duration: 500,
useNativeDriver: true,
}).start();
}
return (
<View>
<View style={validExpenseItem ? [styles.inputsContainer, setAreaDynamicStyling()] : [styles.inputsContainer,styles.shadowProp,setAreaDynamicStyling()]}>
<TextInput
style={styles.textInputArea}
placeholder='Item'
placeholderTextColor="#aaaaaa"
onChangeText={(text) => {onChangeExpenseItem(text)}}
value={expenseItem}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<View style={styles.verticalLine}/>
<CurrencyInput
style={styles.currencyInputArea}
value={expenseValue}
onChangeValue={setExpenseValue}
prefix="£"
delimiter=","
separator="."
precision={2}
minValue={0}
onChangeText={(formattedValue) => {
SetExpenseValueAndSign(formattedValue)
}}
/>
</View>
<AnimatedTouchable onPress={()=>{onButtonPress()}} style={[{flex:1, zIndex:-1},animatedStyle]}>
<View style={[styles.confirmInputContainer, setButtonDynamicStyling(), buttonLayoutPersistence]}>
<Ionicons name="checkmark-circle-outline" size={24} color="white" />
</View>
</AnimatedTouchable>
</View>
)
}
const styles = StyleSheet.create({
confirmInputContainer:{
backgroundColor:"#7f96ff",
height: 48,
flexDirection:"row",
paddingVertical:10,
justifyContent:"center",
},
inputsContainer:{
backgroundColor:"white",
height: 48,
flexDirection:"row",
paddingVertical:10,
marginVertical:10,
justifyContent:"space-between",
},
shadowProp: {
shadowColor: '#353935',
shadowOffset: {width: -2, height: 4},
shadowOpacity: 0.2,
shadowRadius: 4,
},
textInputArea:{
width:"60%",
maxWidth:"60%",
marginLeft:20,
},
verticalLine:{
height: "100%",
width: 1,
backgroundColor: "#909090",
marginHorizontal:5,
},
currencyInputArea:{
maxWidth:"20%",
width:"20%",
marginRight:20
},
})
export default AddAndConfirmExpense;
EDIT:
I have added:
const [animationActive, setAnimationActive] = useState(false);
useEffect(() => {
if (!animationActive && validExpenseItem) {
setButtonLayoutPersistence({bottom:-48})
};
if (!animationActive && !validExpenseItem) {
setButtonLayoutPersistence({bottom:0})
}
},[animationActive])
And changed my show and hide functions to the following:
const showButton = () => {
if (animationActive) return;
setAnimationActive(true)
Animated.timing(animation, {
toValue: -10,
duration: 500,
useNativeDriver: true,
}).start(
() => {
setAnimationActive(false)
}
);
}
const hideButton = () => {
if (animationActive) return;
setAnimationActive(true)
Animated.timing(animation, {
toValue: -58,
duration: 500,
useNativeDriver: true,
}).start(
() => {
setAnimationActive(false)
}
);
}
I have also changed onChangeExpenseItem to :
const onChangeExpenseItem = (text: string) => {
setExpenseItem(text);
if (text.trim().length === 0) {
setValidExpenseItem(false);
hideButton();
setButtonLayoutPersistence({bottom:0})
return;
}
if (!validExpenseItem && !animationActive) {
setValidExpenseItem(true);
showButton();
return
};
};
It is slightly better now, but a better solution is still needed.

Antd Virtual table fixed header

Based on this documentation https://4x.ant.design/components/table/#components-table-demo-virtual-list I've created VirtualTable component.
import React, { useState, useRef, useEffect } from 'react'
import { Table } from 'antd'
import classNames from 'classnames'
import ResizeObserver from 'rc-resize-observer'
import { VariableSizeGrid as Grid } from 'react-window'
const VirtualTable = (props) => {
const { columns, scroll } = props
const [tableWidth, setTableWidth] = useState(0)
const widthColumnCount = columns.filter(({ width }) => !width).length
const mergedColumns = columns.map((column) => {
if (column.width) {
return column
}
return { ...column, width: Math.floor(tableWidth / widthColumnCount) }
})
const gridRef = useRef()
const [connectObject] = useState(() => {
const obj = {}
Object.defineProperty(obj, 'scrollLeft', {
get: () => {
if (gridRef.current) {
return gridRef.current?.state?.scrollLeft
}
return null
},
set: (scrollLeft) => {
if (gridRef.current) {
gridRef.current.scrollTo({
scrollLeft,
})
}
},
})
return obj
})
const resetVirtualGrid = () => {
gridRef?.current?.resetAfterIndices({
columnIndex: 0,
shouldForceUpdate: true,
})
}
useEffect(() => resetVirtualGrid, [tableWidth])
const renderVirtualList = (rawData, { scrollbarSize, ref, onScroll }) => {
ref.current = connectObject
const totalHeight = rawData.length * 54
return (
<Grid
ref={gridRef}
className="virtual-grid"
columnCount={mergedColumns.length}
columnWidth={(index) => {
const { width } = mergedColumns[index]
return totalHeight > scroll.y && index === mergedColumns.length - 1 ? width - scrollbarSize - 1 : width
}}
height={scroll.y}
rowCount={rawData.length}
rowHeight={() => 54}
width={tableWidth}
onScroll={({ scrollLeft }) => {
onScroll({
scrollLeft,
})
}}
>
{({ columnIndex, rowIndex, style }) => (
<div
className={classNames('virtual-table-cell', {
'virtual-table-cell-last': columnIndex === mergedColumns.length - 1,
})}
style={style}
>
{mergedColumns[columnIndex].dataIndex !== '' && !mergedColumns[columnIndex].render ? (
<div> {rawData[rowIndex][mergedColumns[columnIndex].dataIndex]} </div>
) : (
<div>
{mergedColumns[columnIndex].render(
rawData[rowIndex][mergedColumns[columnIndex].dataIndex],
rawData[rowIndex]
)}
</div>
)}
</div>
)}
</Grid>
)
}
return (
<ResizeObserver
onResize={({ width }) => {
setTableWidth(width)
}}
>
<Table
{...props}
className="virtual-table"
columns={mergedColumns}
pagination={false}
components={{
body: renderVirtualList,
}}
/>
</ResizeObserver>
)
}
export default VirtualTable
However, when I tried to create a fixed header like this https://4x.ant.design/components/table/#components-table-demo-fixed-header I broke this table.
Any thoughts on how to implement a fixed header for Antd table based on the virtual table?
UPDATE:
I found that ant table based on https://table-react-component.vercel.app/demo/virtual-list but in my case renderVirtualList is not called

Filter by category react, fetching information from 3 different api's and displaying them in a card format

hopefully you are having a great day!
I am making a website for uni group project,
Logic: I'll post data from database to localhost:3001, this is backend made using node.
Then I'll collect that information into 3 const (foodList, clothesList, techList) and then add value of these const into a variable called 'data'
data will be used to render cards to display on website.
Now the problem I am having is, in Logic.js, that when I made filter for categories, it is partially working, but new data is not rendered and displayed on website
Categories.js:
const catgs = [
{
"_id": 1,
"category": "Food"
},
{
"_id": 2,
"category": "Clothes"
},
{
"_id": 3,
"category": "Tech"
}
]
export {catgs};
CheckBox.js:
import React from 'react'
import { Checkbox, Collapse } from 'antd'
import { useState } from 'react'
function CheckBox(props) {
const { Panel } = Collapse;
const [Checked, setChecked] = useState([])
const catgs = [
{
"_id": 1,
"category": "Food"
},
{
"_id": 2,
"category": "Clothes"
},
{
"_id": 3,
"category": "Tech"
}
];
const handleToggle = (value) => {
const currentIndex = Checked.indexOf(value)
const newChecked = [...Checked]
if(currentIndex === -1){
newChecked.push(value)
}else{
newChecked.splice(currentIndex, 1)
}
setChecked(newChecked)
props.handleFilters(newChecked)
}
const renderCheckboxLists = () => catgs.map((value, index) => (
<React.Fragment key={index}>
<Checkbox
onChange = {() => handleToggle(value._id)}
type="checkbox"
checked= {Checked.indexOf(value._id) === -1 ? false : true }
/>
<span>{value.category}</span>
</React.Fragment>
))
return (
<div>
<Collapse defaultActiveKey={['0']} >
<Panel header="Categories" key="1">
{renderCheckboxLists()}
</Panel>
</Collapse>
</div>
)
}
export default CheckBox
Logic.js:
import React from 'react'
import {useState, useEffect} from "react"
import Axios from 'axios';
import { Col, Card, Row } from 'antd';
import 'antd/dist/antd.css';
import CheckBox from './CheckBox';
const Logic = () => {
const { Meta } = Card;
const [foodList, setfoodList] = useState([]);
const [clothesList, setclothesList] = useState([]);
const [techList, settechList] = useState([]);
let [data, setdata] = useState([])
const [Limit, setLimit] = useState(8);
const [Filters, setFilters] = useState({
category: []
})
useEffect(() => {
Axios.get('http://localhost:3001/api/get/food').then((response) => {
setfoodList(response.data);
});
}, []);
useEffect(() => {
Axios.get('http://localhost:3001/api/get/clothes').then((response) => {
setclothesList(response.data);
});
}, []);
useEffect(() => {
Axios.get('http://localhost:3001/api/get/tech').then((response) => {
settechList(response.data);
});
}, []);
data = [...foodList, ...clothesList, ...techList];
const onLoadMore = () => {
setLimit(Limit + 8);
}
const renderCards = data.slice(0, Limit).map((val, index) => {
return <Col key={index} lg={6} md={8} xs={24}>
<Card
hoverable={true}
cover = {<a href = {val.Link}>{val.Image}</a>}
>
<Meta
title={val.Company}
description={val.Description}
/>
</Card>
</Col>
})
const showFilteredResults = (filters) => {
}
const handleFilters = (filters, category) => {
const newFilters = { ...Filters }
newFilters[category] = filters
showFilteredResults(newFilters)
setFilters(newFilters)
}
return (
<div style = {{ width : '75%', margin: '3rem auto' }}>
<div style = {{ textAlign: 'center' }}>
<h2>Eco Friendly Companies </h2>
</div>
<CheckBox
handleFilters = {filters => handleFilters(filters, "category")}
/>
{data.length === 0 ?
<div style={{ display: 'flex', height: '300px', justifyContent: 'center', alignItems: 'center' }}>
<h2>No post yet...</h2>
</div> :
<div>
<Row gutter={[16, 16]}>
{renderCards}
</Row>
</div>
}
<br></br>
<div style = {{ display: 'flex', justifyContent: 'center'}}>
<button onClick={onLoadMore}>Load More</button>
</div>
</div>
)
}
export default Logic

CheckMap State not updating when unticking checkbox in React

This is my first time to develop a react application. Please bear with me.
I intended to display the key (value in the table) on a confirmation dialog. It works as intended but when I tick then untick the checkbox, it seems that the key is still in the map.
I displayed checkedMap by retrieving its keys and add it in an array. But when I try to untick and then invoke the dialog, checkedMap still has the unticked key, in the debug view.
Thank you for your help.
import React, { useState, useEffect, useRef } from "react";
//import makeData from "../makeData";
import { useTableState } from "react-table";
import Table from "../TransactionPanelTable";
// Simulate a server
const getServerData = async ({ filters, sortBy, pageSize, pageIndex }) => {
await new Promise(resolve => setTimeout(resolve, 500));
// Ideally, you would pass this info to the server, but we'll do it here for convenience
const filtersArr = Object.entries(filters);
// Get our base data
let rows = [];
rows.push({
transaction_seq: 1555,
record_count: 300,
user_id: "test1",
updated_at: "09-MAY-19 10.01.45.371373000 PM",
duration: 5.7
});
rows.push({
transaction_seq: 2666,
rec_count: 1234,
user_id: "test2",
updated_at: "",
duration: 1.23
});
// Apply Filters
if (filtersArr.length) {
rows = rows.filter(row =>
filtersArr.every(([key, value]) => row[key].includes(value))
);
}
// Apply Sorting
if (sortBy.length) {
const [{ id, desc }] = sortBy;
rows = [...rows].sort(
(a, b) => (a[id] > b[id] ? 1 : a[id] === b[id] ? 0 : -1) * (desc ? -1 : 1)
);
}
// Get page counts
const pageCount = Math.ceil(rows.length / pageSize);
const rowStart = pageSize * pageIndex;
const rowEnd = rowStart + pageSize;
// Get the current page
rows = rows.slice(rowStart, rowEnd);
return {
rows,
pageCount
};
};
export default function({ infinite }) {
**const [checkedMap, setCheckedMap] = useState(new Map());**
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const currentRequestRef = useRef();
**let newMap = new Map();**
const fetchData = async () => {
setLoading(true);
// We can use a ref to disregard any outdated requests
const id = Date.now();
currentRequestRef.current = id;
// Call our server for the data
const { rows, pageCount } = await getServerData({
filters,
sortBy,
pageSize,
pageIndex
});
// If this is an outdated request, disregard the results
if (currentRequestRef.current !== id) {
return;
}
// Set the data and pageCount
setData(rows);
setState(old => ({
...old,
pageCount
}));
**rows.forEach(row => newMap.set(row, false));**
//setCheckedMap(newMap);
setLoading(false);
};
**const handleCheckedChange = transaction_seq => {
let modifiedMap = checkedMap;
modifiedMap.set(transaction_seq, !checkedMap.get(transaction_seq));
setCheckedMap(modifiedMap);
};**
const columns = [
{
Header: "Transaction(s)",
className: "left",
columns: [
{
id: "checkbox",
accessor: "checkbox",
Cell: ({ row }) => {
return (
<input
type="checkbox"
className="checkbox"
checked={checkedMap.get(row.original.transaction_seq)}
onChange={() =>
handleCheckedChange(row.original.transaction_seq)
}
/>
);
},
sortable: false,
width: 45
},
{
Header: "Transaction Sequence",
accessor: "transaction_seq",
id: "transaction_seq",
minWidth: 200,
maxWidth: 300
},
{
Header: "Record count",
accessor: "record_count",
width: 300
},
{
Header: "User Id",
accessor: "user_id",
width: 300
},
{
Header: "Updated At",
accessor: "updated_at",
width: 400
},
{
Header: "Duration",
accessor: "duration",
width: 400
}
]
}
];
// Make a new controllable table state instance
const state = useTableState({ pageCount: 0 });
const [{ sortBy, filters, pageIndex, pageSize }, setState] = state;
// When sorting, filters, pageSize, or pageIndex change, fetch new data
useEffect(() => {
fetchData();
}, [sortBy, filters, pageIndex, pageSize]);
return (
<React.Fragment>
<Table
{...{
data,
**checkedMap,**
columns,
infinite,
state, // Pass the state to the table
loading,
manualSorting: true, // Manual sorting
manualFilters: true, // Manual filters
manualPagination: true, // Manual pagination
disableMultiSort: true, // Disable multi-sort
disableGrouping: true, // Disable grouping
debug: true
}}
/>
</React.Fragment>
);
}
Here is the table.js
import styled, { css } from "styled-components";
import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import { FixedSizeList as List } from "react-window";
import {
useTable,
useColumns,
useRows,
useFilters,
useSortBy,
useExpanded,
usePagination,
useFlexLayout
} from "react-table";
export default function MyTable({ loading, infinite, checkedMap, ...props }) {
const instance = useTable(
{
...props
},
useColumns,
useRows,
useFilters,
useSortBy,
useExpanded,
usePagination,
useFlexLayout
);
const {
getTableProps,
headerGroups,
rows,
getRowProps,
pageOptions,
page,
state: [{ pageIndex, pageSize, sortBy, filters }],
gotoPage,
prepareRow,
previousPage,
nextPage,
setPageSize,
canPreviousPage,
canNextPage
} = instance;
const { listRef, rowHeight, height, overscan } = useInfiniteScroll({
enabled: infinite,
sortBy,
filters,
pageIndex,
pageSize
});
let tableBody;
const renderRow = (row, index, style = {}) => {
if (!row) {
return (
<Row {...{ style, even: index % 2 }}>
<Cell>Loading more...</Cell>
</Row>
);
}
prepareRow(row);
return (
<Row {...row.getRowProps({ style, even: index % 2 })}>
{row.cells.map(cell => {
const isPivot = row.groupByID === cell.column.id;
const showAggregate = row.subRows && !isPivot;
return (
<Cell {...cell.getCellProps()}>
{showAggregate ? (
cell.column.aggregate ? (
cell.render("Aggregated")
) : null
) : (
<span>
{isPivot ? (
<span
style={{
cursor: "pointer",
paddingLeft: `${row.depth * 2}rem`,
paddingRight: "1rem",
whiteSpace: "nowrap"
}}
onClick={() => row.toggleExpanded()}
/>
) : null}
{cell.render("Cell")}
{isPivot ? <span> ({row.subRows.length})</span> : null}
</span>
)}
</Cell>
);
})}
</Row>
);
};
if (infinite) {
tableBody = (
<List
ref={listRef}
height={height}
itemCount={rows.length + 1}
itemSize={rowHeight}
overscanCount={overscan}
scrollToAlignment="start"
{...getRowProps()}
>
{({ index, style }) => {
const row = rows[index];
return renderRow(row, index, style);
}}
</List>
);
} else {
tableBody =
page && page.length ? page.map((row, i) => renderRow(row, i)) : null;
}
let pagination;
pagination = pageOptions.length ? (
<Pagination {...getRowProps()}>
<Cell>
<Button onClick={() => previousPage()} disabled={!canPreviousPage}>
Previous
</Button>{" "}
<Button onClick={() => nextPage()} disabled={!canNextPage}>
Next
</Button>{" "}
<span>
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{" "}
</span>
<span>
| Go to page:{" "}
<Input
type="number"
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
gotoPage(page);
}}
style={{ width: "100px" }}
/>
</span>{" "}
<Select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value));
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</Select>{" "}
<Button onClick={() => reprocessConfirmation()}>Reprocess</Button>
</Cell>
</Pagination>
) : null;
function reprocessConfirmation() {
let confirmation = window.confirm(
"Do you want to reprocess transaction sequence " +
Array.from(checkedMap.keys())
);
if (confirmation === true) console.log(Array.from(checkedMap.keys()));
else console.log("CANCEL");
}
return (
<React.Fragment>
<Table {...getTableProps()}>
{headerGroups.map(headerGroup => (
<HeaderRow {...headerGroup.getRowProps()}>
{headerGroup.headers.map(column => (
<Header
{...column.getHeaderProps()}
sorted={column.sorted}
sortedDesc={column.sortedDesc}
sortedIndex={column.sortedIndex}
>
<div>
<span {...column.getSortByToggleProps()}>
{column.render("Header")}
</span>{" "}
</div>
{column.canFilter ? <div>{column.render("Filter")}</div> : null}
</Header>
))}
</HeaderRow>
))}
{tableBody}
<Row {...getRowProps()}>
{loading ? (
<Cell>
<strong>Loading...</strong>
</Cell>
) : (
<Cell>{rows.length} Total Records</Cell>
)}
</Row>
{pagination}
</Table>
</React.Fragment>
);
}

I can not send the text to firebase when Button is click with redux

if i write this onPress={() => this.submit()} instead onPress={() => this.onSendBtnPressed()} i can get the text value but if i just write onPress={() => this.onSendBtnPressed()} this i can't see the text on the message list
here is my chatui
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {KeyboardAvoidingView, Image, TouchableOpacity, ReactNative,
StyleSheet } from 'react-native';
import {Footer, FooterTab} from "native-base";
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-
view';
import { View, Title, Screen, ScrollView, TextInput,Text } from
'#shoutem/ui';
import Messages from '../containers/Messages';
import Input from '../containers/Input';
import { sendMessage } from '../actions';
const mapStateToProps = (state) => ({
chatHeight: state.chatroom.meta.height,
user: state.user,
});
class ChatUI extends Component {
constructor()
{
super();
this.state = {
text: '',
scrollViewHeight: 0,
inputHeight: 0
}
}
updateValue(text) {
console.warn(text)
}
componentDidMount() {
this.scrollToBottom(false);
}
componentDidUpdate() {
this.scrollToBottom();
}
onChangeText(text) {
this.setState({text: text})
};
onSendBtnPressed (text) {
return sendMessage(text, this.props.user)
this.textInput.clear();
Keyboard.dismiss();
}
onScrollViewLayout = (event) => {
const layout = event.nativeEvent.layout;
this.setState({
scrollViewHeight: layout.height
});
}
onInputLayout = (event) => {
const layout = event.nativeEvent.layout;
this.setState({
inputHeight: layout.height
});
}
scrollToBottom(animate = false) {
const { scrollViewHeight, inputHeight } = this.state,
{ chatHeight } = this.props;
const scrollTo = chatHeight - scrollViewHeight + inputHeight;
if (scrollTo > 0) {
this.refs.scroll.scrollToEnd();
}
}
_scrollToInput(reactRef) {
this.refs.scroll.scrollToFocusedInput(ReactNative.findNodeHandle(reactRef));
}
submit() {
let collection = {}
collection.text = this.state.text,
console.warn(collection);
}
render() {
return (
<Screen >
<Title style={{paddingTop: 25, paddingLeft: 10, borderBottomWidth: 0.5, backgroundColor: 'white', paddingBottom: 10}}>
<Image style={{width:80, height: 80}} source={require('../img/11.png')} />
<Text> GENERAL CHAT</Text>
</Title>
<KeyboardAwareScrollView ref="scroll"
onLayout={this.onScrollViewLayout}>
<Messages />
</KeyboardAwareScrollView>
<Footer style={{width:360,
height:30,
backgroundColor:'#fff',
borderRadius:20,
borderWidth: 0.5,
marginBottom: 3,
borderColor: 'gray',
}}>
<TextInput
onSubmitEditing = {this.onSubmitEditing}
multiline
onLayout={this.onInputLayout}
submitAction={this.onSendBtnPressed}
ref="input"
placeholder="Say something ..."
onChangeText={(text) => this.onChangeText(text, 'text')}
style={{backgroundColor: 'rgba(0,0,0,0)',
borderBottomWidth: 1.5, borderColor: '#f8f8f8', fontSize: 16, color: 'gray', paddingBottom:20}}
ref={input => { this.textInput = input; } }/>
</Footer>
<TouchableOpacity
onPress={() => this.onSendBtnPressed()}
style={{marginLeft:280, backgroundColor:'gray', width: 70, height: 30}}
title= "send"
>
</TouchableOpacity>
</Screen>
)
}
}
export default connect(mapStateToProps, {sendMessage})(ChatUI);
and here is my actions
import firebase from '../firebase';
import DeviceInfo from 'react-native-device-info';
import {Actions} from 'react-native-router-flux';
import FCM, { FCMEvent, NotificationType, WillPresentNotificationResult,
RemoteNotificationResult } from 'react-native-fcm';
import { Platform } from 'react-native';
export const addMessage = (msg) => ({
type: 'ADD_MESSAGE',
...msg
});
export const sendMessage = (text, user) => {
return function (dispatch) {
let msg = {
text: text,
time: Date.now(),
author: {
name: user.name,
avatar: user.avatar
}
};
const newMsgRef = firebase.database()
.ref('messages')
.push();
msg.id = newMsgRef.key;
newMsgRef.set(msg);
dispatch(addMessage(msg));
};
};
export const startFetchingMessages = () => ({
type: 'START_FETCHING_MESSAGES'
});
export const receivedMessages = () => ({
type: 'RECEIVED_MESSAGES',
receivedAt: Date.now()
});
export const fetchMessages = () => {
return function (dispatch) {
dispatch(startFetchingMessages());
firebase.database()
.ref('messages')
.orderByKey()
.limitToLast(20)
.on('value', (snapshot) => {
setTimeout(() => {
const messages = snapshot.val() || [];
dispatch(receiveMessages(messages))
}, 0);
});
}
}
export const receiveMessages = (messages) => {
return function (dispatch) {
Object.values(messages).forEach(msg => dispatch(addMessage(msg)));
dispatch(receivedMessages());
}
}
export const updateMessagesHeight = (event) => {
const layout = event.nativeEvent.layout;
return {
type: 'UPDATE_MESSAGES_HEIGHT',
height: layout.height
}
}
export const setUserName = (name) => {
return (dispatch) => {
dispatch({
type: 'SET_USER_NAME',
payload: name
});
};
};
export const setUserAvatar = (avatar) => ({
type: 'SET_USER_AVATAR',
avatar: avatar && avatar.length > 0 ? avatar : 'avatar.jpg'
});
export const login = () => {
return function (dispatch, getState) {
dispatch(startAuthorizing());
firebase.auth()
.signInAnonymously()
.then(() => {
const { name, avatar } = getState().user;
firebase.database()
.ref(`users/`)
.push({
name,
avatar
});
dispatch(userAuthorized());
dispatch(fetchMessages());
});
}
}
export const checkUserExists = () => {
return function (dispatch) {
dispatch(startAuthorizing());
firebase.auth()
.signInAnonymously()
.then(() => firebase.database()
.ref(`users/${DeviceInfo.getUniqueID()}`)
.once('value', (snapshot) => {
const val = snapshot.val();
if (val === null) {
dispatch(userNoExist());
}else{
dispatch(setUserName(val.name));
dispatch(setUserAvatar(val.avatar));
startChatting(dispatch);
}
}))
.catch(err => console.log(err))
}
}
const startChatting = function (dispatch) {
dispatch(userAuthorized());
dispatch(fetchMessages());
FCM.requestPermissions();
FCM.getFCMToken()
.then(token => {
console.log(token)
});
FCM.subscribeToTopic('secret-chatroom');
FCM.on(FCMEvent.Notification, async (notif) => {
console.log(notif);
if (Platform.OS === 'ios') {
switch (notif._notificationType) {
case NotificationType.Remote:
notif.finish(RemoteNotificationResult.NewData); //other
types available: RemoteNotificationResult.NewData,
RemoteNotificationResult.ResultFailed
break;
case NotificationType.NotificationResponse:
notif.finish();
break;
case NotificationType.WillPresent:
notif.finish(WillPresentNotificationResult.All); //other
types available: WillPresentNotificationResult.None
break;
}
}
});
FCM.on(FCMEvent.RefreshToken, token => {
console.log(token);
});
}
export const startAuthorizing = () => ({
type: 'USER_START_AUTHORIZING'
});
export const userAuthorized = () => ({
type: 'USER_AUTHORIZED'
});
export const userNoExist = () => ({
type: 'USER_NO_EXIST'
});
and MessageList
import React, { Component } from 'react';
import {
ListView, Text, Row, Image,
View, Subtitle, Caption, Heading
} from '#shoutem/ui';
import moment from 'moment';
const Message = ({ msg }) => (
<Row>
<Image styleName="small-avatar top"
source={{ uri: msg.author.avatar }} />
<View styleName="vertical">
<View styleName="horizontal space-between">
<Subtitle>{msg.author.name}</Subtitle>
<Caption>{moment(msg.time).from(Date.now())}</Caption>
</View>
<Text styleName="multiline">{msg.text}</Text>
</View>
</Row>
);
const MessageList = ({ messages, onLayout }) => (
<ListView data={messages}
autoHideHeader={true}
renderRow={msg => <Message msg={msg} />}
onLayout={onLayout}
/>
);
export default MessageList;
You are not passing a text variable into your onSendButtonPressed function. It looks like you should be calling it using the following syntax:
onPress={() => this.onSendBtnPressed(someText)}
Your onSendBtnPressed() function has the return statement on the first line, so the code will not be fired underneath the return statement (not related to your question, but hope to fix any other headaches...)

Categories