the idea of this stuff is that user can add parameters to the SQL editor from two inputs, one for the parameter itself and the other one for its value
and if user writes in SQL editor, automatically adds inputs that are the parameter and the value.
From SQL editor to inputs it works fine, cause I'm sending a regex. The trouble is located where I try to send the inputs to SQL Editor. I think is more like a logic problem, but I still don't find the solution.
The point is that by adding one parameter from the input, it adds more than required, even though I always clean it before adding, but apparently that doesn't affect.
This is the code
import React, { useEffect, useState} from 'react';
import SQLContainerInput from '../Components/SQLContainerInput';
........
function arrayParamsExec(stringSql) {
const paramsQueryText = [...stringSql.matchAll(/{{(\w+)}}/ig)];
const newArray = paramsQueryText.map(item => item[1]);
return newArray;
}
const initalStateCurrentChartInfo = {
SQLQuery: '',
dataType: 'TABLE',
columns: [],
};
const CustomSQLEditor = ({
fromQuery, // del Redux
}) = {
const [currentChartInfo, setCurrentChartInfo] = useState(
initalStateCurrentChartInfo,
);
const [params, setParams] = useState([]);
const [textSql, setTextSql] = useState('');
useEffect(() => {
....
let sqlDefaultString = '';
sqlDefaultString = fromQuery.internal_name
? `SELECT * FROM \`${fromQuery.internal_name}__${fromQuery.items[0]}\` LIMIT 20`
: '';
setCurrentChartInfo({
...currentChartInfo,
SQLQuery: `${sqlQuery}`,
});
},[fromQuery]);
// ------------------params---------------------
const addProperty = () => {
setParams([
...params,
{ name: '', value: '' },
]);
};
const updateProperty = (event, index, key) => {
const newProperties = [...params];
newProperties[index][key] = event?.target?.value;
// agregar parámetros al editor SQL
let sqlParams = textSql;
if (key === 'name') {
params.forEach(p => {
if (p.name && /^\w+$/i.test(p.name)) {
sqlParams += `{{${p.name}}}`;
}
});
setTextSql('');
setTextSql(`${sqlParams}`);
}
setParams(newProperties);
};
const deleteProperty = index => {
const newProperties = [...params];
newProperties.splice(index, 1);
const newTextSQL = replaceAll(textSql, `{{${params[index]?.name}}}`, '');
setTextSql(newTextSQL);
setParams(newProperties);
};
// ------------------end params---------------------
const changeTextEditor = (valueEditor) => {
const namesParams = arrayParamsExec(valueEditor);
const newProperties = namesParams.map((pName) => {
const valueNew = params.find(p => p.name === pName);
return {name: pName, value: valueNew?.value || ''};
});
setParams(newProperties);
setTextSql(valueEditor);
}
return (
<>
<SQLContainerInput
button={{
onClick: handleSubmit,
}}
input={{
value: `${textSql}\n`,
onChange: changeTextEditor,
}}
/>
<DymanicKeyValueInputInput
properties={params}
updateProperty={updateProperty}
deleteProperty={deleteProperty}
addProperty={addProperty}
/>
</>
);
}
Then, I tried as a solution set another value which is textSql, that takes care of placing the concatenated string, and the string coming from redux is fromQuery. The redux string is set in the sqlParams variable, when is added concatenates with the params and then, I clean textSql
......
const updateProperty = (event, index, key) => {
const newProperties = [...params];
newProperties[index][key] = event?.target?.value;
// agregar parámetros al editor SQL
let sqlParams = currentChartInfo.SQLQuery;
if (key === 'name') {
params.forEach(p => {
if (p.name && /^\w+$/i.test(p.name)) {
sqlParams += `{{${p.name}}}`;
}
});
setTextSql('');
setTextSql(`${sqlParams}`);
}
setParams(newProperties);
};
......
The trouble in there is that if I directly write from SQL editor, it resets the whole string, I mean, everything that has been written but there it works when I put the params and it's not repeated. I don't find a way to do that, so I'm sorry for the ignorance if I'm doing something wrong.
For example, when I write a large SQL text.
When a parameter is added from the input, it resets.
Video with the error demo: https://www.youtube.com/watch?v=rQBPOPyeXlI
Repo's url: https://gitlab.com/albert925/parametrosui-a-editor-sql
try this:
import { useState, useEffect } from 'react';
import DymanicKeyValueInputInput from './components/DymanicKeyValueInputInput';
import SQLContainerInput from './components/SQLContainerInput';
import { fromQuery } from './bd/data';
import { replaceAll, arrayParamsExec } from './utils/strings';
import { Content, ContentAddButton } from './stylesApp';
const initalStateCurrentChartInfo = {
SQLQuery: '',
columns: [],
};
function App() {
const [currentChartInfo, setCurrentChartInfo] = useState(
initalStateCurrentChartInfo,
);
const [params, setParams] = useState([]);
const [textSql, setTextSql] = useState('');
const [endSql, setendSql] = useState('');
useEffect(() => {
let sqlQuery = '';
sqlQuery = fromQuery.internal_name
? `SELECT * FROM \`${fromQuery.internal_name}__${fromQuery.items[0]}\` LIMIT 20`
: '';
setCurrentChartInfo({
...currentChartInfo,
SQLQuery: `${sqlQuery}`,
});
setTextSql(sqlQuery);
},[fromQuery]);
useEffect(()=>{
const endSql = replaceAll(textSql, '\n', '');
setendSql('')
setendSql(endSql);
},[textSql]);
const cleanSQLString = (string) => {
//corto la cadena en {{
//reemplazo {{ por "" en cada item
//devuelvo el array con los reemplazos
return string.split("{{").map(item=>item.replace("}}",""));
}
// ------------------params---------------------
const addProperty = () => {
setParams([
...params,
{ name: '', value: '' },
]);
};
const updateProperty = (event, index, key) => {
const newProperties = [...params];
newProperties[index][key] = event?.target?.value;
let currentSQL = cleanSQLString(endSql);
// clean the string so that then add parameters to sql
if (key === 'name' && /^\w+$/i.test(event?.target?.value)) {
let sqlParams;
if(currentSQL.length > 1){
sqlParams = `${currentSQL[0]}`;
}else{
sqlParams = `${endSql}`;
}
params.forEach(p => {
if (p.name && /^\w+$/i.test(p.name)) {
sqlParams += `{{${p.name}}}`;
}
});
setTextSql('');
setTextSql(`${sqlParams}`);
/*if(currentSQL.length > 1){
sqlParams = `${currentSQL[0]}`;
}else{
sqlParams = `${endSql}`;
}
console.log(sqlParams)
// get parameter positions by regular expression
const searchParamRegedix = arrayParamsExec(endSql);
console.log(searchParamRegedix, 'searchParamRegedix')
if (searchParamRegedix.paramsQueryText.length > 0) {
console.log(111)
// get the position
const searchPosition = searchParamRegedix.paramsQueryText[index]?.index;
// remove the old word in braces
const deleteOldParam = replaceAll(sqlParams, `${searchParamRegedix.list[index]}`, '');
// the remaining string is removed from the obtained position
const deleteFirtsLetter = deleteOldParam.substr(searchPosition + 2);
// the string is removed from the beginning to the position obtained
const deleteRemaining = deleteOldParam.substr(0, searchPosition + 2)
// the string of the beginning and end is combined with its parameter
const contantString = deleteRemaining + event?.target?.value + deleteFirtsLetter;
// the entire string is overwritten in the state
setTextSql(contantString);
}
else{
params.forEach(p => {
if (p.name && /^\w+$/i.test(p.name)) {
sqlParams += `{{${p.name}}}`;
}
});
setTextSql('');
setTextSql(`${sqlParams}`);
}*/
}
setParams(newProperties);
};
const deleteProperty = index => {
const newProperties = [...params];
newProperties.splice(index, 1);
const newTextSQL = replaceAll(textSql, `{{${params[index]?.name}}}`, '');
setTextSql(newTextSQL);
setParams(newProperties);
};
// ------------------end params---------------------
const changeTextEditor = (valueEditor) => {
const namesParams = arrayParamsExec(valueEditor);
// keep the value with the parameter with respect to adding a new parameter in the sql editor,
// but if the parameter word is changed, the value is deleted
const newProperties = namesParams.list.map((pName) => {
const valueNew = params.find(p => p.name === pName);
return {name: pName, value: valueNew?.value || ''};
});
setParams(newProperties);
setTextSql(valueEditor);
}
return (
<Content>
<div>
<SQLContainerInput
input={{
value: `${endSql}\n`,
onChange: changeTextEditor,
}}
/>
</div>
<div>
<h2>PARAMS</h2>
<ContentAddButton>
<DymanicKeyValueInputInput
properties={params}
updateProperty={updateProperty}
deleteProperty={deleteProperty}
addProperty={addProperty}
id="sqlapiparams"
className="isMultipleInpustSelects"
tilesColumns={["", ""]}
inputsPlaceholder={["My_Parameter", "Type params value"]}
isIconTransfer
isIconError
/>
</ContentAddButton>
</div>
</Content>
);
}
export default App;
Related
I'm a bit confused
I am sending emails with nodemailer, and every time I send one I perform certain validations in order to manage the upload limit of the attachments. If the upload limit exceeds what is established, the service divides that email and sends it in different emails with the same subject and body as well as its attachment.
Every time this happens, it does a _.chunk that takes care of splitting the pdfs array into smaller elements. But, it should be noted that before that, he made a method to prepare the attachments and this is in charge of obtaining certain information from the api to paint the pdf buffer and thus put it in the content of the emails.
But now what I want to do is search within the matrix that performs the step before dividing the files those that are equal to the array that obtains the information and if they are equal, carry out the instruction that it sends
I will explain with a graph:
If getAmount.pdfBuffer === attachmentMap
// doAction console.log('Equals)
But even though I tried to do it, I couldn't, I don't know if it's because for each attachment that the array has divided, it generates a getAmount array. What do you think I'm doing wrong?
async sendEmail(
{
para: to,
asunto: subject,
plantilla: template,
contexto: context,
}: CorreoInfoDto,
attachments: EmailAttachment[],
driveConfig: OAuthGoogleConfig
) {
const totalSize: number = this.getSizeFromAttachments(attachments);
const chunkSplit = Math.floor(isNaN(totalSize) ? 1 : totalSize / this.LIMIT_ATTACHMENTS) + 1;
const attachmentsChunk: any[][] = _.chunk(attachments, chunkSplit);
if ((totalSize > this.LIMIT_ATTACHMENTS) && attachmentsChunk?.length >= 1) {
await Promise.all(
attachmentsChunk?.map(async (attachment: EmailAttachment[], index) => {
console.log('attachment', attachment)
if (this.getSizeFromAttachments(attachment) > this.LIMIT_ATTACHMENTS) {
const result: GenerateDriveLinkResponse[] = await Promise.all(
attachment?.map(item => {
const file = new GoogleDriveUploadFile({
name: item?.filename,
mimeType: MimeTypesEnum.PDF,
body: item?.content
});
return this.uploadFilesService.uploadToDrive(driveConfig, file) as any;
})
)
const texto = result?.map((item, index) => {
console.log('item', item?.webViewLink);
console.log('index', index);
return new SolicitudXLinkDrive({
texto: attachment[index].filename,
link: item?.webViewLink
})
});
context.links = texto;
const link = `(${index + 1}/${attachmentsChunk?.length - 1})`;
const newContext = {
getCurrent: link,
...context
}
const prepareEmail = this.prepareEmail({
para: to,
asunto: ` ${subject} (${index + 1}/${attachmentsChunk?.length})`,
plantilla: template,
contexto: newContext,
}, []);
return prepareEmail
} else {
// this.getCantidad = `(${index + 1}/${attachmentsChunk?.length - 1})`;
console.log('getCantidad', this.getAmount );
const attachmentMap = attachment.map(element => element.content);
this.getAmount .forEach(element => {
if (element.pdfBuffer === attachmentMap) {
console.log('do action');
}
})
const link = ` (${index + 1}/${attachmentsChunk?.length - 1})`;
const newContext = {
getCurrent: link,
...context
}
return this.prepareEmail({
para: to,
asunto: ` ${subject} (Correo ${index + 1}/${attachmentsChunk?.length - 1})`,
plantilla: template,
contexto: newContext,
}, attachment);
}
})
);
} else {
await this.prepareEmail(
{
para: to,
asunto: ` ${subject}`,
plantilla: template,
contexto: context,
},
attachments,
);
}
}
async prepareEmail(
{
para: to,
asunto: subject,
plantilla: template,
contexto: context,
}: CorreoInfoDto,
attachments: EmailAttachment[],
) {
return await this.mailerService.sendMail({
to,
from: `${process.env.SENDER_NAME} <${process.env.EMAIL_USER}>`,
subject,
template,
attachments: attachments,
context: context,
});
}
async sendEmails(correos: EnvioMultiplesCorreosDto) {
let pdf = null;
let info: ConfiguracionDocument = null;
let GDriveConfig: ConfiguracionDocument = null;
let logo: ConfiguracionDocument = null;
let forContext = {};
const documents = Array.isArray(correos.documento_id) ? correos.documento_id : [correos.documento_id];
const solicitudes = await this.solicitudesService.findByIds(documents);
const nombresPacientes = solicitudes.reduce((acc, cv) => {
acc[cv.correlativo_solicitud] = cv['info_paciente']?.nombre_paciente;
return acc;
}, {});
await Promise.all([
await this.getPdf(correos.tipo_reporte, correos.documento_id, correos?.dividir_archivos).then(data => { pdf = data; }),
await this.configuracionesService.findByCodes([
ConfigKeys.TEXTO_CORREO_MUESTRA,
ConfigKeys[process.env.DRIVE_CONFIG_API],
ConfigKeys.LOGO_FIRMA_PATMED
]).then(data => {
info = data[0];
GDriveConfig = data[1];
logo = data[2];
})
]);
forContext = this.configuracionesService.castValorObjectToObject(info?.valor_object)
const attachmentPrepare = this.prepareAttachments(pdf as any, nombresPacientes);
await this.sendEmail(
{
para: correos.para,
asunto: correos.asunto,
plantilla: 'muestras',
contexto: {
cuerpo: correos.cuerpo,
titulo: forContext[EnvioCorreoMuestraEnum.titulo],
direccion: forContext[EnvioCorreoMuestraEnum.direccion],
movil: forContext[EnvioCorreoMuestraEnum.movil],
pbx: forContext[EnvioCorreoMuestraEnum.pbx],
email: forContext[EnvioCorreoMuestraEnum.email],
logo: logo?.valor,
},
},
attachmentPrepare,
this.configuracionesService.castValorObjectToObject(GDriveConfig?.valor_object) as any,
);
const usuario = new UsuarioBitacoraSolicitudTDTO();
usuario.createFromUserRequest(this.sharedService.getUserFromRequest());
solicitudes.forEach((solicitud) => {
const actual = new BitacoraSolicitudDTO();
actual.createFromSolicitudDocument(solicitud);
const newBitacora = new CrearBitacoraSolicitudDTO();
newBitacora.createNewItem(null, actual, actual, usuario, AccionesBitacora.EmailEnviado);
this.bitacoraSolicitudesService.create(newBitacora);
});
}
prepareAttachments(item: BufferCorrelativosDTO | BufferXSolicitudDTO[], nombresPacientes: { [key: string]: string }) {
if (this.sharedService.isAnArray(item)) {
const castItem: BufferXSolicitudDTO[] = item as any;
this.getCantidad = castItem;
return castItem?.map((s) => {
const namePatient = nombresPacientes[s.correlativo_solicitud];
return new EmailAttachment().setFromBufferXSolicitudDTO(s, namePatient, 'pdf');
});
} else {
return [new EmailAttachment().setFromBufferCorrelativosDTO(item as any, 'pdf')];
}
}
Thank you very much for your attention, I appreciate it. Cheers
You could try using lodash as this has _.intersectionBy and _.intersectionWith functions that should allow you to compare 2 arrays and filter the common values.
There are some good examples here:
How to get intersection with lodash?
I have used following code in my component to load new data when scrolling.But when new page of data is loaded the scroll bar keeps returning to the top.I'm using this for Magento PWA with react.
const Category = props => {
const {id} = props;
const classes = mergeClasses(defaultClasses, props.classes);
const [paginationValues, paginationApi] = usePagination();
const {currentPage, totalPages} = paginationValues;
const {setCurrentPage, setTotalPages} = paginationApi;
const [selectedPageSize, setPageSize] = useState(40);
const [pageNumber, setPageNumber] = useState(1);
function handlePageSize() {
setPageNumber(pageNumber+1)
}
const sortProps = useSort();
const [currentSort] = sortProps;
const previousSort = useRef(currentSort);
const pageControl = {
currentPage,
setPage: setCurrentPage,
totalPages
};
const [runQuery, queryResponse] = useLazyQuery(GET_CATEGORY,{fetchPolicy:'cache-first'});
const {loading, error, data} = queryResponse;
const {search} = useLocation();
const bqueryResponsee = useQuery(
GET_ATTRIBUTES
);
let battributedata = '';
// Keep track of the search terms so we can tell when they change.
const previousSearch = useRef(search);
// Get "allowed" filters by intersection of schema and aggregations
const {data: introspectionData} = useQuery(FILTER_INTROSPECTION);
// Create a type map we can reference later to ensure we pass valid args
// to the graphql query.
// For example: { category_id: 'FilterEqualTypeInput', price: 'FilterRangeTypeInput' }
const filterTypeMap = useMemo(() => {
const typeMap = new Map();
if (introspectionData) {
introspectionData.__type.inputFields.forEach(({name, type}) => {
typeMap.set(name, type.name);
});
}
return typeMap;
}, [introspectionData]);
// Run the category query immediately and whenever its variable values change.
useEffect(() => {
window.addEventListener('scroll', infiniteScroll);
// Wait until we have the type map to fetch product data.
if (!filterTypeMap.size) {
return;
}
const filters = getFiltersFromSearch(search);
const newFilters = {};
newFilters['category_id'] = {eq: String(id)};
filters.forEach((values, key) => {
newFilters[key] = getFilterInput(values, filterTypeMap.get(key));
});
runQuery({
variables: {
currentPage: Number(pageNumber),
id: Number(id),
filters: newFilters,
pageSize: Number(selectedPageSize),
sort: {[currentSort.sortAttribute]: currentSort.sortDirection}
}
});
window.scrollTo({
left: 0,
top: 0,
behavior: 'smooth'
});
}, [
currentPage,
currentSort,
filterTypeMap,
id,
selectedPageSize,
runQuery,
search,
pageNumber
]);
const totalPagesFromData = data
? Math.ceil(data.category.product_count/selectedPageSize)
: null;
useEffect(() => {
setTotalPages(totalPagesFromData);
return () => {
setTotalPages(null);
};
}, [setTotalPages, totalPagesFromData]);
// If we get an error after loading we should try to reset to page 1.
// If we continue to have errors after that, render an error message.
useEffect(() => {
if (error && !loading && currentPage !== 1) {
setCurrentPage(1);
}
}, [currentPage, error, loading, setCurrentPage]);
// Reset the current page back to one (1) when the search string, filters
// or sort criteria change.
useEffect(() => {
// We don't want to compare page value.
const prevSearch = new URLSearchParams(previousSearch.current);
const nextSearch = new URLSearchParams(search);
prevSearch.delete('page');
nextSearch.delete('page');
if (
prevSearch.toString() !== nextSearch.toString() ||
previousSort.current.sortAttribute.toString() !==
currentSort.sortAttribute.toString() ||
previousSort.current.sortDirection.toString() !==
currentSort.sortDirection.toString()
) {
// The search term changed.
setCurrentPage(1);
// And update the ref.
previousSearch.current = search;
previousSort.current = currentSort;
}
}, [currentSort, previousSearch, search, setCurrentPage]);
if (error && currentPage === 1 && !loading) {
if (process.env.NODE_ENV !== 'production') {
console.error(error);
}
return <div>Data Fetch Error</div>;
}
// Show the loading indicator until data has been fetched.
if (totalPagesFromData === null) {
return fullPageLoadingIndicator;
}
if (typeof bqueryResponsee.data !== "undefined" && !bqueryResponsee.loading) {
battributedata = bqueryResponsee.data.customAttributeMetadata.items[0].attribute_options;
} else {
battributedata = [];
}
const count = totalPagesFromData ? totalPagesFromData : null;
const galleries = [];
if (count && count >= 1) {
for (let i =1;i<=pageNumber;i++) {
galleries.push(
<section className={classes.gallery}>
<Gallery searchItems={null} ID={1}
newSort={{[currentSort.sortAttribute]: currentSort.sortDirection}}
newFilters={newFiltersLazy}
currentPage={i} categoryId={id} pageSize={40} introspectionData={introspectionData}
battributedata={battributedata}/>
</section>
)
}
}
function infiniteScroll(){
console.log(Math.ceil((window.innerHeight + document.documentElement.scrollTop)/100)*100,Math.ceil((document.documentElement.offsetHeight*8/10)/100)*100)
// End of the document reached?
if ( Math.ceil((window.innerHeight + document.documentElement.scrollTop)/100)*100
>= Math.ceil((document.documentElement.offsetHeight*8/10)/100)*100){
handlePageSize()
}
}
return (
<Fragment>
<Meta name="description" content={metaDescription}/>
<CategoryContent
totalPagesFromData={totalPagesFromData}
categoryId={id}
classes={classes}
data={loading ? null : data}
pageControl={pageControl}
sortProps={sortProps}
onSelectSize={handlePageSize}
battributedata={battributedata}
selectedPageSize={selectedPageSize}
galleries={galleries}
newSort={{[currentSort.sortAttribute]: currentSort.sortDirection}}
newFilters={newFiltersLazy}
introspectionData={introspectionData}
pageNumber={pageNumber}
/>
</Fragment>
);
};
What I did here is created a new array galleries and the Gallery component in each page increment while scrolling and inside Gallery the items will be created.Problem is the scroll bar keep returning to the top in each increment.Please help
I am a beginner in React working on a Attendance-System Project.(Sorry for all the bad code)
So I have this component called ShowData, and it receives some props from it's parent component. I am using conditional rendering in this. So I have only one state variable called filteredUsers, and it's initialized to an empty array.
Now what I want to do is fetch all the User Data from firebase and once I have all the users, I set my state equal to that. So for that I have a useEffect()
which has this async function called init()
Now the problem is that, when I try to use filteredUsers.length in the conditional rendering part, it gives me 0.
import React from "react";
import { db } from "./firebase";
import { dateStripper, getDaysArray } from "./utils";
import "./ShowData.css";
import MusterUser from "./MusterUser";
function ShowData(props) {
let { userid, date, format, employee } = props;
let [filteredUsers, setFilteredUsers] = React.useState([]);
console.log(date);
React.useEffect(() => {
console.log("UseEffect triggered");
async function getUserData(employeeID, currentDate) {
let attendanceDoc = await db
.collection(userid)
.doc("Att-Records")
.collection(currentDate)
.doc(employeeID)
.get()
.then((doc) => {
if (!doc.exists) {
return { data: null, currentDate: currentDate };
}
return { data: doc.data(), currentDate: currentDate };
});
return attendanceDoc;
}
async function getEmployees() {
return await db
.collection(userid)
.doc("Employee-Records")
.collection("UUID-Data")
.get()
.then((employeeSnapshot) => {
let temp = [];
employeeSnapshot.forEach((employeeDoc) => {
temp.push(employeeDoc);
});
return temp;
});
}
async function init() {
let allDates = [];
let musterUsers = [];
let dates = getDaysArray(date[0], date[1]);
if (date[0] === date[1]) {
dates.pop();
}
dates.forEach((date) => {
allDates.push(dateStripper(date));
});
if (format === "muster" && employee === "all") {
let serialNum = 0;
let users = await getEmployees();
users.forEach(async (user) => {
serialNum = serialNum + 1;
let tempUser = {
SNo: serialNum,
name: user.data()["Employee Name"],
dept: user.data()["Employee Department"],
desg: user.data()["Employee Designation"],
total: 0,
present: 0,
absent: 0,
miss: 0,
};
let calls = [];
allDates.forEach(async (date) => {
calls.push(getUserData(user.id, date));
});
let userPerDates = await Promise.all(calls);
userPerDates.forEach((callbackResult) => {
let data = callbackResult.data;
let currentDate = callbackResult.currentDate;
if (data) {
tempUser["total"] = tempUser["total"] + 1;
if (data["Status"] === "A") {
tempUser[currentDate] = "Absent";
tempUser["absent"] = tempUser["absent"] + 1;
} else if (data["Status"] === "P") {
tempUser[currentDate] = "Present";
tempUser["present"] = tempUser["present"] + 1;
} else if (data["Status"] === "MIS") {
tempUser[currentDate] = "MIS";
tempUser["miss"] = tempUser["miss"] + 1;
}
} else {
tempUser[currentDate] = "N/A";
}
});
musterUsers.push(tempUser);
});
return musterUsers;
}
}
if (date.length) {
console.log("init triggered");
init().then((musterUsers) => {
setFilteredUsers(musterUsers);
});
} else {
console.log("not triggered");
}
}, [date, format, employee]);
if (!date.length) {
console.log("NoDate");
return (
<div className="noData">
<h1>Nothing to show</h1>
</div>
);
} else {
console.log(filteredUsers);
console.log(format);
let allDates = [];
let dates = getDaysArray(date[0], date[1]);
if (date[0] === date[1]) {
dates.pop();
}
dates.forEach((date) => {
allDates.push(dateStripper(date));
});
console.log("I reached here");
console.log(filteredUsers.length);
if (format === "muster" && filteredUsers.length > 0) {
console.log("Main function runs");
return (
<div className="data">
<div className="data__header">
<h1>Muster Roll</h1>
<h1>
From {allDates[0]} to {allDates[allDates.length - 1]}
</h1>
</div>
<div className="data__box">
<div className="data__columns">
<li>Employee Name</li>
<li>Employee Department</li>
<li>Employee Designation</li>
{allDates.map((allDates) => (
<li className="dateColumn">{allDates}</li>
))}
<li>Total</li>
<li>Present</li>
<li>Absent</li>
<li>Miss</li>
</div>
<div className="muster__users">
<MusterUser user={filteredUsers} />
</div>
</div>
</div>
);
} else {
return <h1>OOPS</h1>;
}
}
}
export default ShowData;
Refer to the Using the State Hook
const [myState, setMyState] = React.useState([]);
...
newArray = ["Hello", "World", "Know"]
setMyState(newArray)
...
React.useEffect(() => {
console.log(myState)
}, [ myState ])
...
return
(<div>
{!date.length ?
<div className="noData">
<h1>Nothing to show</h1>
</div> :
<div>
....
</div>
}
</div>)
instead of pushing elements one by one to the array, just use a temporary array and update it any number of times you want and then finally make your state equal to that array.
I learn react and js myself. please explain why this situation occurs. PS: excuse me for the large text, I tried to explain the problem as clearly as possible. thanks. Essence of the matter: set the initial state through the hook:
const [pokemon, setPokemon] = useState({
img: "",
name: "",
types: [],
abilities: [],
moveList: [],
weight: "",
height: "",
description: "",
genus: "",
chanceToCatch: "",
evolutionURL: ""
});
further I make api requests to get information from inside useEffect:
useEffect(() => {
const fetchData = async () => {
await Axios({
method: "GET",
url: urlPokemonAPI
})
.then(result => {
const pokemonResponse = result.data;
/* Pokemon Information */
const img = pokemonResponse.sprites.front_default;
const name = pokemonResponse.name;
const weight = Math.round(pokemonResponse.weight / 10);
const height = pokemonResponse.height / 10;
const types = pokemonResponse.types.map(type => type.type.name);
const abilities = pokemonResponse.abilities.map(
ability => ability.ability.name
);
const moveList = pokemonResponse.moves.map(move => move.move.name);
setPokemon(() => {
return {
img: img,
name: name,
weight: weight,
types: types,
abilities: abilities,
moveList: moveList,
height: height
};
});
})
await Axios({
method: "GET",
url: urlPokemonSpecies
}).then(result => {
let description = "";
result.data.flavor_text_entries.forEach(flavor => {
if (flavor.language.name === "en") {
description = flavor.flavor_text;
}
});
let genus = "";
result.data.genera.forEach(genera => {
if (genera.language.name === "en") {
genus = genera.genus;
}
});
const evolutionURL = result.data.evolution_chain.url;
const eggGroups = result.data.egg_groups.map(
egg_group => egg_group.name
);
const chanceToCatch = Math.round(
(result.data.capture_rate * 100) / 255
);
setPokemon(pokemon => {
return {
...pokemon,
description: description,
genus: genus,
chanceToCatch: chanceToCatch,
evolutionURL: evolutionURL,
eggGroups: eggGroups
};
});
});
};
fetchData();
}, [urlPokemonAPI, urlPokemonSpecies]);
The problem arises specifically with eggGroups (with identical handling of abilities and types there is no such problem). And this is what happens when I want to output data to a page as <div> Egg Group: {pokemon.eggGroups} </div> the data is displayed normally, but as soon as I want to output eggGroups as well as abilities and types separated by commas (join ( ',')) - error: TypeError: pokemon.eggGroups is undefined. I decided to check this matter through the console and stuffed this eggGroups key into the timeout:
At some point, eggGroups becomes undefined ... why, I can’t understand. But if I set the state separately, like const [egg, setEgg] = useState ([]); setEgg (eggGroups); such a problem is not observed. why is this happening? everything was fine with types and abilities. Thank you in advance.
state updater from hooks doesn't merge the state values when updating state, instead it just replaces the old value with new one
Since you use state updater like
setPokemon(() => {
return {
img: img,
name: name,
weight: weight,
types: types,
abilities: abilities,
moveList: moveList,
height: height
};
});
eggGroups property is lost and hence it becomes undefined. You need to update it by spreading the previous state values obtained from callback
setPokemon((prev) => {
return {
...prev
img: img,
name: name,
weight: weight,
types: types,
abilities: abilities,
moveList: moveList,
height: height
};
});
Your code have a problem, this is the proper way to do await with axios,
you need to import axios like this
import axios from 'axios';
the await should be call with a promise, then it return the data from api like this:
const result = await axios.get(urlPokemonAPI);
This is the code snippet with the same logic to your code
useEffect(() => {
const fetchData = async () => {
// import axios from 'axios';
try {
const result = await axios.get(urlPokemonAPI);
const pokemon = result.data;
setPokemon({
img: pokemon.sprites.front_default,
name: pokemon.name,
weight: Math.round(pokemon.weight / 10),
types: pokemon.types.map(i => i.type.name),
abilities: pokemon.abilities.map(i => i.ability.name),
moveList: pokemon.moves.map(i => i.move.name),
height: pokemon.height / 10
});
const result2 = await axios.get(urlPokemonSpecies);
const data = result2.data;
let description = "";
data.flavor_text_entries.forEach(i => {
const lang = i.language.name
if (lang === "en") {
description = i.flavor_text;
}
});
let genus = "";
data.genera.forEach(i => {
const lang = i.language.name;
if (lang === "en") {
genus = i.genus;
}
});
setPokemon(pokemon => {
return {
...pokemon,
description,
genus,
chanceToCatch: Math.round((data.capture_rate * 100) / 255),
evolutionURL,
eggGroups: data.egg_groups.map(g => g.name)
};
});
} catch (e) {
console.log(e);
}
};
fetchData();
}, [urlPokemonAPI, urlPokemonSpecies]);
do you see another problem: you call setPokemon two times, let's rewrite it again:
useEffect(() => {
const fetchData = async () => {
// import axios from 'axios';
try {
const result = await axios.get(urlPokemonAPI);
const data1 = result.data;
const result2 = await axios.get(urlPokemonSpecies);
const data2 = result2.data;
function resolveDescription(data) {
let description = "";
data.flavor_text_entries.forEach(i => {
const lang = i.language.name
if (lang === "en") {
description = i.flavor_text;
}
});
return description;
}
function resolveGenus(data) {
let genus = "";
data.genera.forEach(i => {
const lang = i.language.name;
if (lang === "en") {
genus = i.genus;
}
});
return genus;
}
setPokemon({
img: data1.sprites.front_default,
name: data1.name,
weight: Math.round(data1.weight / 10),
types: data1.types.map(i => i.type.name),
abilities: data1.abilities.map(i => i.ability.name),
moveList: data1.moves.map(i => i.move.name),
height: data1.height / 10,
description: resolveDescription(data2),
genus: resolveGenus(data2),
chanceToCatch: Math.round((data2.capture_rate * 100) / 255),
evolutionURL: data2.evolution_chain.url,
eggGroups: data2.egg_groups.map(g => g.name)
});
} catch (e) {
console.log(e);
}
};
fetchData();
}, [urlPokemonAPI, urlPokemonSpecies]);
My FlatList does not update when the props I pass from redux change. Every time I send a message I increase everyones unread message count in both firebase and in my redux store. I made sure to include key extractor and extra data, but neither helps. The only thing that changes the unread message count is a reload of the device. How do I make sure the flatList updates with MapStateToProps. I made sure to create a new object by using Object.Assign:
action:
export const sendMessage = (
message,
currentChannel,
channelType,
messageType
) => {
return dispatch => {
dispatch(chatMessageLoading());
const currentUserID = firebaseService.auth().currentUser.uid;
let createdAt = firebase.database.ServerValue.TIMESTAMP;
let chatMessage = {
text: message,
createdAt: createdAt,
userId: currentUserID,
messageType: messageType
};
FIREBASE_REF_MESSAGES.child(channelType)
.child(currentChannel)
.push(chatMessage, error => {
if (error) {
dispatch(chatMessageError(error.message));
} else {
dispatch(chatMessageSuccess());
}
});
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child(channelType)
.child(currentChannel).child('users')
UNREAD_MESSAGES.once("value")
.then(snapshot => {
snapshot.forEach(user => {
let userKey = user.key;
// update unread messages count
if (userKey !== currentUserID) {
UNREAD_MESSAGES.child(userKey).transaction(function (unreadMessages) {
if (unreadMessages === null) {
dispatch(unreadMessageCount(currentChannel, 1))
return 1;
} else {
alert(unreadMessages)
dispatch(unreadMessageCount(currentChannel, unreadMessages + 1))
return unreadMessages + 1;
}
});
} else {
UNREAD_MESSAGES.child(userKey).transaction(function () {
dispatch(unreadMessageCount(currentChannel, 0))
return 0;
});
}
}
)
})
};
};
export const getUserPublicChannels = () => {
return (dispatch, state) => {
dispatch(loadPublicChannels());
let currentUserID = firebaseService.auth().currentUser.uid;
// get all mountains within distance specified
let mountainsInRange = state().session.mountainsInRange;
// get the user selected mountain
let selectedMountain = state().session.selectedMountain;
// see if the selected mountain is in range to add on additional channels
let currentMountain;
mountainsInRange
? (currentMountain =
mountainsInRange.filter(mountain => mountain.id === selectedMountain)
.length === 1
? true
: false)
: (currentMountain = false);
// mountain public channels (don't need to be within distance)
let currentMountainPublicChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Public");
// mountain private channels- only can see if within range
let currentMountainPrivateChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Private");
// get public channels
return currentMountainPublicChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
let publicChannelsToDownload = [];
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
// add the channel ID to the download list
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child("Public")
.child(channelId).child('users').child(currentUserID)
UNREAD_MESSAGES.on("value",snapshot => {
if (snapshot.val() === null) {
// get number of messages in thread if haven't opened
dispatch(unreadMessageCount(channelId, 0));
} else {
dispatch(unreadMessageCount(channelId, snapshot.val()));
}
}
)
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
// flag whether you can check in or not
if (currentMountain) {
dispatch(checkInAvailable());
} else {
dispatch(checkInNotAvailable());
}
// if mountain exists then get private channels/ if in range
if (currentMountain) {
currentMountainPrivateChannelsRef
.orderByChild("key")
.on("value", snapshot => {
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child("Public")
.child(channelId).child('users').child(currentUserID)
UNREAD_MESSAGES.on("value",
snapshot => {
if (snapshot.val() === null) {
// get number of messages in thread if haven't opened
dispatch(unreadMessageCount(channelId, 0));
} else {
dispatch(unreadMessageCount(channelId, snapshot.val()));
}
}
)
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
});
}
return publicChannelsToDownload;
})
.then(data => {
setTimeout(function () {
dispatch(loadPublicChannelsSuccess(data));
}, 150);
});
};
};
Reducer:
case types.UNREAD_MESSAGE_SUCCESS:
const um = Object.assign(state.unreadMessages, {[action.info]: action.unreadMessages});
return {
...state,
unreadMessages: um
};
Container- inside I hook up map state to props with the unread messages and pass to my component as props:
const mapStateToProps = state => {
return {
publicChannels: state.chat.publicChannels,
unreadMessages: state.chat.unreadMessages,
};
}
Component:
render() {
// rendering all public channels
const renderPublicChannels = ({ item, unreadMessages }) => {
return (
<ListItem
title={item.info.Name}
titleStyle={styles.title}
rightTitle={(this.props.unreadMessages || {} )[item.id] > 0 && `${(this.props.unreadMessages || {} )[item.id]}`}
rightTitleStyle={styles.rightTitle}
rightSubtitleStyle={styles.rightSubtitle}
rightSubtitle={(this.props.unreadMessages || {} )[item.id] > 0 && "unread"}
chevron={true}
bottomDivider={true}
id={item.Name}
containerStyle={styles.listItemStyle}
/>
);
};
return (
<View style={styles.channelList}>
<FlatList
data={this.props.publicChannels}
renderItem={renderPublicChannels}
keyExtractor={(item, index) => index.toString()}
extraData={[this.props.publicChannels, this.props.unreadMessages]}
removeClippedSubviews={false}
/>
</View>
);
}
}
Object.assign will merge everything into the first object provided as an argument, and return the same object. In redux, you need to create a new object reference, otherwise change is not guaranteed to be be picked up. Use this
const um = Object.assign({}, state.unreadMessages, {[action.info]: action.unreadMessages});
// or
const um = {...state.unreadMessages, [action.info]: action.unreadMessages }
Object.assign() does not return a new object. Due to which in the reducer unreadMessages is pointing to the same object and the component is not getting rerendered.
Use this in your reducer
const um = Object.assign({}, state.unreadMessages, {[action.info]: action.unreadMessages});