Reactjs functional component common logic extraction - javascript

I have a few functional components with a few common variables and functions. Please see below.
const Customer = () => {
const [isReleased, setIsReleased] = useState(false)
const release = () => {
setIsReleased(true)
}
}
const Order = () => {
const [isReleased, setIsReleased] = useState(false)
const release = () => {
setIsReleased(true)
}
}
As you can see from the above code fragment, the release() function has common logic. It accesses component's variables/functions.
Is there a way to move this release() function to a common file and import it from each component?
Please note that the release() method should be able to access scoped variables and functions of the caller.
Update
Below is the actual content of the release() function. I have put this. to denote that it refers to the variables/functions in the caller.
const release = () => {
if (action === "new") {
history.push(`/customers/new`)
} else if (action === "save") {
(async () => {
try {
if (this.dataMode === "new") {
this.setMessage()
this.setFormStatus("updating")
let _res = await this.customer_api_create(this.formData)
if ((_res.status === 200) && (_res.data.status === "success")) {
this.setFormStatus()
history.replace({ pathname: `/customers/${_res.data.data[0].id}` })
}
} else if (this.dataMode === "edit") {
this.setFormStatus("updating")
this.setMessage()
let _res = await this.customer_api_update(this.formData)
if ((_res.status === 200) && (_res.data.status === "success")) {
this.setFormStatus()
this.setMessage({ type: "info", text: "Saved" })
this.setFormData(_res.data.data[0])
}
}
} catch (e) {
this.openMessageBox({
prompt: e.response.data.message,
type: this.constants.app.MessageBoxType.MB_Error,
buttons: this.constants.app.MessageBoxButton.MB_Ok,
show: true,
setResult: () => console.log(this.constants.app.MessageBoxResult.MB_Ok)
})
this.setFormStatus("updating_error")
}
})();
} else if (action === "del") {
this.openMessageBox({
prompt: `{ "": ["Are you sure you want to delete context customer?"] }`,
type: this.constants.app.MessageBoxType.MB_Warning,
buttons: this.constants.app.MessageBoxButton.MB_YesNo,
show: true,
setResult: (val) => {
this.openMessageBox({ show: false })
this.setShouldRecordDeleted(val)
}
})
}
}

You can declare the function release in a different file and pass the setIsReleased method as an argument
import {release} from 'release';
const Customer = () => {
const [isReleased, setIsReleased] = useState(false)
release(setIsReleased)
}
And in your release.js file
export const release = (setIsReleased) => {
setIsReleased(true)
}

Maybe callback solve the problem, how about this?
anotherfile.js
const release = (param, setter) => {
if (true){
setter(true)
}
}
yourfile.js
import {release} from "anotherfile"
const Customer = () => {
const [isReleased, setIsReleased] = useState(false)
release([parameter],setIsReleased)
}

Related

Problem when I try to run two react-query in a row

I have two different endpoints, one that is called with getProjectMapping and one with getStaffing. The getProjectMapping query must be run first in order to set the project variable, which will then be used to make the getStaffing request. But I get the following error:
Uncaught TypeError: project is null
I get that error in the getStaffing request, although before activating it I check that the project is not null. Does anyone know what is wrong?
const Staffing = () => {
const { tokenApi } = useContext(LoginContext);
const [project, setProject] = useState(null);
const {
data: projectMapping,
isLoading: projectMappingIsLoading,
isFetching,
} = useQuery("ProjectMapping", () => getProjectMapping(tokenApi), {
onSuccess: () => {
if (projectMapping != null && projectMapping.length !== 0) {
setProject(projectMapping[0]);
}
},
});
const { data, isLoading } = useQuery(
[project.value, "Staffing"],
() => getStaffing(project.value, tokenApi),
{
enabled: !isFetching && project != null,
dependencies: [project],
}
);
}
This isn't how you structure dependent queries.. Instead of setting state you should derive it. If you have dependent queries it might also make sense to wrap them in a custom hook
e.g.
const useProjectStaffing = (tokenApi) => {
const {
data: [project] = [],
isLoading: projectMappingIsLoading,
} = useQuery("ProjectMapping", () => getProjectMapping(tokenApi), {
},
});
const projectValue = project && project.value
return useQuery(
[projectValue, "Staffing"],
() => getStaffing(projectValue, tokenApi),
{ enabled: !!projectValue }
);
}
const Staffing = () => {
const { tokenApi } = useContext(LoginContext);
const {isLoading, data: staffing} = useProjectStaffing(tokenApi);
// ... do stuff with the staffing data when it comes back.

React state updates when using object, but not when using array

I have a question about some weird behavior when using React state.
If I change the default type of the groupedFormErrors variable in the helper function to [] instead of {} the state in the page element formErrors doesn't update. Would be great if somebody could explain why this happens.
Page element
const [formErrors, setFormErrors] = useState();
const signUpHandler = async (e) => {
e.preventDefault();
const response = await submitForm();
if (response.status === 422) {
setFormErrors(groupFormErrors(response));
}
};
Helper function
export default function GroupFormErrors(response) {
let groupedFormErrors = {}; // <<<<<<<<<<<<<<<<<<<<< This here
if (response.status === 422) {
response.data.forEach((error) => {
if (groupedFormErrors.hasOwnProperty(error.param)) {
groupedFormErrors[error.param].push(error.msg);
} else {
groupedFormErrors[error.param] = [];
groupedFormErrors[error.param].push(error.msg);
}
});
}
return groupedFormErrors;
}

Rendered fewer hooks than expected. This may be caused by an accidental early return statement

I'm getting this error when triggering a setState inside of a custom React hook. I'm not sure of how to fix it, can anyone show me what I'm doing wrong. It is getting the error when it hits handleSetReportState() line. How should I be setting the report state from inside the hook?
custom useinterval poll hook
export function usePoll(callback: IntervalFunction, delay: number) {
const savedCallback = useRef<IntervalFunction | null>()
useEffect(() => {
savedCallback.current = callback
}, [callback])
useEffect(() => {
function tick() {
if (savedCallback.current !== null) {
savedCallback.current()
}
}
const id = setInterval(tick, delay)
return () => clearInterval(id)
}, [delay])
}
React FC
const BankLink: React.FC = ({ report: _report }) => {
const [report, setReport] = React.useState(_report)
if ([...Statues].includes(report.status)) {
usePoll(async () => {
const initialStatus = _report.status
const { result } = await apiPost(`/links/search` });
const currentReport = result.results.filter((item: { id: string; }) => item.id === _report.id)
if (currentReport[0].status !== initialStatus) {
handleSetReportState(currentReport[0])
console.log('status changed')
} else {
console.log('status unchanged')
}
}, 5000)
}
... rest
This is because you put usePoll in if condition, see https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
You can put the condition into the callback
usePoll(async () => {
if ([...Statues].includes(report.status)) {
const initialStatus = _report.status
const { result } = await apiPost(`/links/search` });
const currentReport = result.results.filter((item: { id: string; }) => item.id === _report.id)
if (currentReport[0].status !== initialStatus) {
handleSetReportState(currentReport[0])
console.log('status changed')
} else {
console.log('status unchanged')
}
}
}, 5000)
And if the delay will affect report.status, use ref to store report.status and read from ref value in the callback.

How do I pass index number to a react class?

Need help passing data "locationpos"= index of my Locations[] from function to class. I'm very new to React and I'm not sure what I'm doing wrong.
ERROR
Failed to compile
./src/components/data.js
Line 20:30: 'locationpos' is not defined no-undef
Search for the keywords to learn more about each error.
This error occurred during the build time and cannot be dismissed.
class Data {
constructor(locationpos) {
this.locationpos=locationpos;
this.updateData();
}
getTimes(date = null) {
date = date === null ? moment().format('DD/MM/YYYY') : date;
var data = this.getData();
return data ? data[date] : [];
}
getSpeadsheetUrl() {
return config.myData[locationpos];
}
function Daily({ locationProps = 1, root }) {
const context = useContext(ThemeContext);
const localization = useCallback(() => {
if (root && cookies.get("location") !== undefined) {
return cookies.get("location");
}
return locationProps;
}, [locationProps, root]);
const [locationState] = useState(localization());
const handleClick = event => {
window.focus();
notification.close(event.target.tag);
};
const openNav = () => {
document.getElementById("sidenav").style.width = "100%";
};
const closeNav = e => {
e.preventDefault();
document.getElementById("sidenav").style.width = "0";
};
// eslint-disable-next-line
const locationpos = locations.indexOf(locations[locationState]);
const _data = useRef(new Data(locationpos));
const getTimes = () => _data.current.getTimes();
Inside your data class, you need to use the instance variable as this.locationPos
getSpeadsheetUrl() {
return config.myData[this.locationpos];
}

vscode.commands.executeCommand was not working

I'm writing an VS Code extension to help migrating React.createClass to class extends React.Component. The problem here was, I could not get vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', ...) to work.
Note that the code below is pure JavaScript, but not TypeScript.
function activate(context) {
context.subscriptions.push(vscode.commands.registerCommand('migrate-to-react-es6-class', () => {
const editor = vscode.window.activeTextEditor
const document = editor.document
try {
const originalCode = document.getText()
const modifiedCode = 'do something and return new code'
if (originalCode === modifiedCode) {
vscode.window.showInformationMessage('Nothing is to be migrated.')
} else {
editor.edit(edit => {
const editingRange = document.validateRange(new vscode.Range(0, 0, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER))
edit.replace(editingRange, modifiedCode)
})
if (document.isUntitled === false) {
vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', document.uri, { insertSpaces: true, tabSize: 2 })
}
}
} catch (error) {
vscode.window.showErrorMessage(error.message)
console.error(error)
}
}))
}
After 3.25 years, you've probably figured this out by now, but for the record, I assume you hung a .then() on the editor.edit() and then moved the executeCommand to within the then(), right?
editor.edit(edit => {
const editingRange = ...
edit.replace(editingRange, modifiedCode)
}).then(editWorked => {if (editWorked && !document.isUntitled) {
vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', ...) })
You must apply returned edits
private async formatDocument(): Promise<void> {
const docUri = this.textEditor.document.uri;
const textEdits = (await vscode.commands.executeCommand(
'vscode.executeFormatDocumentProvider',
docUri,
)) as vscode.TextEdit[];
const edit = new vscode.WorkspaceEdit();
for (const textEdit of textEdits) {
edit.replace(docUri, textEdit.range, textEdit.newText);
}
await vscode.workspace.applyEdit(edit);
}
I implemented it directly in the extension.ts:
commands.registerCommand(constants.commands.formatDocument, async () => {
const docUri = editor?.document.uri;
const textEdits: TextEdit[] | undefined = await commands.executeCommand(
'vscode.executeFormatDocumentProvider',
docUri
);
if (textEdits && docUri) {
const edit = new WorkspaceEdit();
for (const textEdit of textEdits) {
edit.replace(docUri, textEdit.range, textEdit.newText);
}
await workspace.applyEdit(edit);
}
});
constants.commands.formatDocument gets the value after parsing my package.json

Categories