can't get latlng from react useref - react-leaflet

Recently, I just started utilizing leftlet in my React ts project. I want to implement a draggable marker on the map. However, I cannot do anything with the return ref.
const [position, setPosition] = React.useState(TEMP.center);
const markerRef = React.useRef(null);
const eventHandlers = React.useMemo(
() => ({
dragend() {
const marker = markerRef.current;
if (marker != null) {
setPosition(marker.getLatLng());
}
},
}),
[]
);
and I get this error
Property 'getLatLng' does not exist on type 'never'. TS2339
50 | const marker = markerRef.current;
51 | if (marker != null) {
> 52 | setPosition(marker.getLatLng());
| ^
53 | }
54 | },
55 | }),

Change line 2 to:
const markerRef = useRef<any>(null)
I ran into the same issue, this fixed it for me. Thanks to jnelson for the tip.

Related

How do I iterate through selected cells in AgGrid?

I would like to delete the values in all selected cells when the user presses backspace or delete, how do I iterate through the selected cells?
I'm using React and Typescript
The docs are out of date, this worked for me, in react / typescript
(
// The type of this is shown below, but is impossible to work with
event: any
// | CellKeyPressEvent<CountryLineProductRow>
// | FullWidthCellKeyPressEvent<CountryLineProductRow>
) => {
if (event.event.key === 'Backspace' || event.event.key === 'Delete') {
event.api.getCellRanges().forEach((cellRange: CellRange) => {
if (cellRange.startRow && cellRange.endRow) {
const startRow = Math.min(
cellRange.endRow.rowIndex,
cellRange.startRow.rowIndex
);
const endRow = Math.min(
cellRange.endRow.rowIndex,
cellRange.startRow.rowIndex
);
cellRange.columns.forEach((column: Column) => {
if (column.getColDef().editable) {
for (
let rowNumber = startRow;
rowNumber <= endRow;
rowNumber++
) {
const model = event.api.getModel();
const rowNode = model.rowsToDisplay[rowNumber];
rowNode.setDataValue(column.getColDef().field, null);
}
}
});
}
});
}
}

How do I generate SEO URL's using Instantsearch

I'm using Algolia's Instantsearch.js with Typesense adapter, which works well for my search page, but if I go to the search page with a query or refinement in the URL, it doesn't filter. For example, if I do https://example.com/buy-list/buying/Sports (sports being the category), it doesn't automatically filter.
Here is the relevant snippet. I've narrowed it down to somewhere in the router because if I set it router: true, it works, it's just not pretty URL's.
const search = instantsearch({
searchClient,
indexName: INSTANT_SEARCH_INDEX_NAME,
routing: {
router: instantSearchRouter({
windowTitle({category, query}) {
const queryTitle = query ? `Results for "${query}"` : 'Buy List | DA Card World';
if (category) {
return `${category} – ${queryTitle}`;
}
return queryTitle;
},
createURL({qsModule, routeState, location}) {
const urlParts = location.href.match(/^(.*?)\/buying\/buy-list/);
const baseUrl = `${urlParts ? urlParts[1] : ''}/`;
const categoryPath = routeState.category
? `${getCategorySlug(routeState.category)}/`
: '';
const queryParameters = {};
if (routeState.query) {
queryParameters.query = encodeURIComponent(routeState.query);
}
if (routeState.page !== 1) {
queryParameters.page = routeState.page;
}
if (routeState.types) {
queryParameters.types = routeState.types.map(encodeURIComponent);
}
if (routeState.years) {
queryParameters.years = routeState.years.map(encodeURIComponent);
}
if (routeState.series) {
queryParameters.series = routeState.series.map(encodeURIComponent);
}
const queryString = qsModule.stringify(queryParameters, {
addQueryPrefix: true,
arrayFormat: 'repeat'
});
return `${baseUrl}buying/buy-list/${categoryPath}${queryString}`;
},
parseURL({qsModule, location}) {
const pathnameMatches = location.pathname.match(/\/buying\/buy-list\/(.*?)\/?$/);
const category = getCategoryName(
(pathnameMatches && pathnameMatches[1]) || ''
);
const {query = '', page, types = [], years = [], series = []} = qsModule.parse(
location.search.slice(1)
);
// `qs` does not return an array when there's a single value.
const allTypes = Array.isArray(types) ? types : [types].filter(Boolean);
const allYears = Array.isArray(years) ? years : [years].filter(Boolean);
const allSeries = Array.isArray(series) ? series : [series].filter(Boolean);
return {
query: decodeURIComponent(query),
page,
types: allTypes.map(decodeURIComponent),
years: allYears.map(decodeURIComponent),
series: allSeries.map(decodeURIComponent),
category
};
}
}),
stateMapping: {
stateToRoute(uiState) {
const indexUiState = uiState[INSTANT_SEARCH_INDEX_NAME] || {};
return {
query: indexUiState.query,
page: indexUiState.page,
types: indexUiState.refinementList && indexUiState.refinementList.package,
years: indexUiState.refinementList && indexUiState.refinementList.year,
series: indexUiState.refinementList && indexUiState.refinementList.series,
category: indexUiState.hierarchicalMenu && indexUiState.hierarchicalMenu[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE]
&& indexUiState.hierarchicalMenu[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE].join('>')
};
},
routeToState(routeState) {
return {
instant_search: {
query: routeState.query,
page: routeState.page,
hierarchicalMenu: {
"category": routeState.category && routeState.category.split('>')
},
refinementList: {
packages: routeState.types,
years: routeState.years,
series: routeState.series
}
}
};
}
}
},
searchFunction: function (helper) {
helper.search();
const title = document.querySelector('title');
const header = document.querySelector('#results_title');
let titleText = 'Buy Lists';
if (INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE in helper.state.hierarchicalFacetsRefinements) {
titleText = helper.state.hierarchicalFacetsRefinements[INSTANT_SEARCH_HIERARCHICAL_ATTRIBUTE] + ' Buy List';
}
title.text = titleText + ' | DA Card World';
header.innerText = titleText;
}
});

Pulling Gmail "from:mailer-damon" and "from:postmaster" email data into google sheets?

I came across the code below in an article. I tried to modify it (q: "from:mailer-daemon OR from:postmaster",) so that the code can pull from not only mailer-daemon, but also postmaster emails. However, it handled the data differently for postmaster emails (postmaster recipient emails were put into the "Bounce Reason" column along with the reason). I'd also like for it to pull from a label I've created in Gmail called "Delete from CMMC List", but I'm unsure how to do that.
The column names that data is being separated into are as follows:
Bounce Date
Email Recipient
Error Status
Bounce Reason
Gmail Link
Original code that needs modification:
/*
* Gmail Bounce Report
* u/labnol Feb 12, 2020
* Written by Amit Agarwal
* email: amit#labnol.org
* twitter: u/labnol
* web: www.labnol.org
*/
const toast_ = e => SpreadsheetApp.getActiveSpreadsheet().toast(e);
const parseMessage_ = messageId => {
const message = GmailApp.getMessageById(messageId);
const body = message.getPlainBody();
const [, failAction] = body.match(/^Action:\s*(.+)/m) || [];
if (failAction === "failed") {
const emailAddress = message.getHeader("X-Failed-Recipients");
const [, errorStatus] = body.match(/^Status:\s*([.\d]+)/m) || [];
const [, , bounceReason] =
body.match(/^Diagnostic-Code:\s*(.+)\s*;\s*(.+)/m) || [];
if (errorStatus && bounceReason) {
return [
message.getDate(),
emailAddress,
errorStatus,
bounceReason.replace(/\s*(Please|Learn|See).+$/, ""),
`=HYPERLINK("${message.getThread().getPermalink()}";"View")`
];
}
}
return false;
};
const writeBouncedEmails_ = (data = []) => {
if (data.length > 0) {
toast_("Writing data to Google Sheet..");
const sheet = SpreadsheetApp.getActiveSheet();
sheet
.getRange(3, 1, sheet.getLastRow(), sheet.getLastColumn())
.clearContent();
sheet.getRange(3, 1, data.length, data[0].length).setValues(data);
SpreadsheetApp.flush();
toast_("Bounce Report is ready!");
showCredit_();
}
};
const findBouncedEmails_ = () => {
try {
const rows = [];
const { messages = [] } = Gmail.Users.Messages.list("me", {
q: "from:mailer-daemon",
maxResults: 200
});
if (messages.length === 0) {
toast_("No bounced emails found in your mailbox!");
return;
}
toast_("Working..");
for (let m = 0; m < messages.length; m += 1) {
const bounceData = parseMessage_(messages[m].id);
if (bounceData) {
rows.push(bounceData);
}
if (rows.length % 10 === 0) {
toast_(`${rows.length} bounced emails found so far..`);
}
}
writeBouncedEmails_(rows);
} catch (error) {
toast_(error.toString());
}
};
const onOpen = e => {
SpreadsheetApp.getUi()
.createMenu("🕵🏻‍♂️ Bounced Emails")
.addItem("Run Report", "findBouncedEmails_")
.addSeparator()
.addItem("Credits", "showCredit_")
.addToUi();
};
const showCredit_ = () => {
const template = HtmlService.createHtmlOutputFromFile("help");
const html = template
.setTitle("Bounce Report for Gmail")
.setWidth(460)
.setHeight(225);
SpreadsheetApp.getActiveSpreadsheet().show(html);
}

Seconds not defined in cloud function?

For the last few days, my cloud-function was running perfectly - every minute, the firestore would be queried for old posts and then they would be deleted, as such :
exports.hourly_job = functions.pubsub.topic('hourly-tick').onPublish((change,context) => {
const currentTime = Date.now()
const getPostsForDate = admin.firestore().collection('posts').where('timeOfDeletion', '<', currentTime)
return getPostsForDate.get().then(snapshot => {
const updates = {}
const batch = admin.firestore().batch()
snapshot.forEach((doc) => {
var key = doc.id
console.log(key)
const convos = admin.database().ref('/convoID/' + key).once('value', (snapshot) => {
if (snapshot.exists){
const convos = snapshot.val()
snapshot.forEach((child) => {
updates["conversations/" + child.key] = null
updates["messages/"+ child.key] = null
updates["convoID/"+ child.key] = null
})
}
})
updates["/convoID/"+ key] = null
updates["/reveals/" + key] = null
updates["/postDetails/" + key] = null
const postFireStoreRef = admin.firestore().collection('posts').doc(key)
const posterRef = admin.firestore().collection('posters').doc(key)
batch.delete(postFireStoreRef)
batch.delete(posterRef)
})
return Promise.all[admin.database().ref().update(updates), batch.commit()]
})
})
However, I started receiving the message :
TypeError: Cannot read property 'seconds' of null
at Function.fromProto (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/build/src/timestamp.js:91:46)
at _firestore.request.then.resp (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/build/src/write-batch.js:472:42)
EDIT: I fixed the date error by updating my firebase cloud functions, but the seconds is undefined still persists.
Basically, I just had to not return the batch update if there was nothing to be updated. That fixed the problem.

Draft.js. How can I update block atomic:image src later on for example article save?

I am having trouble updating my image blocks in editorState in draft.js.
I want to change atomic:image src on button save.
So the src is for example now blob:http://localhost:3000/7661d307-871b-4039-b7dd-6efc2701b623
but I would like to update to src to for example /uploads-from-my-server/test.png
onSave(e) {
e.preventDefault();
const { editorState } = this.state;
const contentState = editorState.getCurrentContent();
editorState.getCurrentContent().getBlockMap().map((block) => {
const type = block.getType();
if (type === 'atomic:image') {
const rangeToReplace = new SelectionState({
anchorKey: block.getKey(),
focusKey: block.getKey(),
});
Modifier.replaceText(contentState, rangeToReplace, '/uploads-from-my-server/test.png');
const newContentState = editorState.getCurrentContent();
this.setState({ editorState: newContentState });
}
return true;
});
I know I can access src string with block.getData().get('src') but I cant set though
Thank you for your awesome editor
I was struggling with a similar problem, I ended up converting the content state to raw array using convertToRaw and then updating it manually and use convertFromRaw and set the new state :
import {EditorState, ContentState, convertToRaw, convertFromRaw /*, ...*/} from 'draft-js';
// ...
onSave(e) {
e.preventDefault();
const { editorState } = this.state;
const contentState = editorState.getCurrentContent();
let rawContent = convertToRaw(contentState);
for(let i = 0; i < rawContent.blocks.length; i++) {
let b = rawContent.blocks[i];
if(b['type'] !== "unstyled" && b.entityRanges.length === 1) {
const entityKey = b['entityRanges'][0]['key'];
const entityMap = rawContent['entityMap'][entityKey];
if(entityMap["type"] === "image") {
rawContent['entityMap'][entityKey]['data']['src'] = '/uploads-from-my-server/test.png';
}
}
}
const newContentState = convertFromRaw(rawContent);
const newEditorState = EditorState.push(this.state.editorState, newContentState, 'update-contentState');
this.setState({editorState: newEditorState});
}
Note: This is not a fully working example, it's just a starting point. Hope it helps :)