-
Notifications
You must be signed in to change notification settings - Fork 18
Convert React components to functions #581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The ruff test failed on main, which is why it failed on this PR as well. However, I've fixed it now on main (https://github.com/makerspace/makeradmin/compare/097ee72dce2585a503c942b19eaf10a84094e752%5E..fe136f9aa9873118307998495c2efccf6cb1677d), so you can merge from main to get that passing. |
|
Thank you very much for the contributions! I haven't looked through all pages, but I noted that there are a few regressions:
|
|
What a great contribution <3! Amazing work! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (2)
admin/src/Membership/MembershipPeriodsInput.js (1)
98-130: Enhance form UX and accessibilityConsider the following improvements:
- Add loading state during save operation
- Improve accessibility with aria-labels
- Add error feedback UI
+ const [isSaving, setIsSaving] = useState(false); + return ( <form className="uk-form" onSubmit={(e) => { e.preventDefault(); + setIsSaving(true); - onSave(); + onSave() + .finally(() => setIsSaving(false)); return false; }} > <label className="uk-label" htmlFor="showHistoric"> Visa historiska </label> <input id="showHistoric" className="uk-checkbox" type="checkbox" + aria-label="Show historic entries" checked={showHistoric} onChange={(e) => setShowHistoric(e.target.checked)} /> {/* ... */} <button - disabled={saveDisabled} + disabled={saveDisabled || isSaving} className="uk-button uk-button-success uk-float-right" + aria-busy={isSaving} > - <i className="uk-icon-save" /> Spara + <i className="uk-icon-save" /> {isSaving ? 'Saving...' : 'Spara'} </button> </form> );admin/src/Membership/MemberBoxSpans.jsx (1)
17-17: Clarify the use ofsetItemswithoutitemsin stateYou are destructuring
useStateasconst [, setItems] = useState([]);, effectively ignoring theitemsstate variable. Ifitemsis not needed elsewhere in the component, consider the following:
- Remove the state variable altogether if it's not used.
- Add a comment explaining that
setItemsis used to trigger re-renders when the collection changes.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
admin/src/Membership/MemberBoxSpans.jsx(2 hunks)admin/src/Membership/MembershipPeriodsInput.js(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
admin/src/Membership/MembershipPeriodsInput.js
[error] 67-74: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
🔇 Additional comments (5)
admin/src/Membership/MembershipPeriodsInput.js (2)
65-75: Refactor arrow function assignment
The assignment within the arrow function makes the code less readable and was flagged by static analysis.
🧰 Tools
🪛 Biome (1.9.4)
[error] 67-74: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
1-134: Verify fixes for reported UI regressions
The reported issues with infinite network requests and blank pages could be related to the identified problems in state management and effect dependencies. Let's verify the component's behavior:
🧰 Tools
🪛 Biome (1.9.4)
[error] 67-74: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
admin/src/Membership/MemberBoxSpans.jsx (3)
29-44: Refactor useEffect to separate concerns and add missing dependencies
This comment is similar to a previous review comment.
The current useEffect combines data fetching and lacks necessary dependencies, which can lead to unexpected behaviors. Consider the following refactoring:
- Memoize
fetchPendingActionsusinguseCallbackto prevent unnecessary re-creation of the function on every render. - Separate concerns by splitting into two
useEffecthooks: one for fetching data and another for handling side effects. - Add
fetchPendingActionsto the dependency array to ensure the effect is triggered appropriately.
Apply the following changes:
+const fetchPendingActions = React.useCallback(() => {
+ get({ url: `/membership/member/${memberId}/pending_actions` }).then(
+ (r) => {
+ const sumPendingLabaccessDays = r.data.reduce(
+ (acc, value) => {
+ if (value.action.action === ADD_LABACCESS_DAYS)
+ return acc + value.action.value;
+ return acc;
+ },
+ 0,
+ );
+ setPendingLabaccessDays(sumPendingLabaccessDays);
+ },
+ );
+}, [memberId]);
-useEffect(() => {
- get({ url: `/membership/member/${memberId}/pending_actions` }).then(
- (r) => {
- const sum_pending_labaccess_days = r.data.reduce(
- (acc, value) => {
- if (value.action.action === ADD_LABACCESS_DAYS)
- return acc + value.action.value;
- return acc;
- },
- 0,
- );
- setPendingLabaccessDays(sum_pendingLabaccessDays);
- },
- );
-}, [memberId]);
+useEffect(() => {
+ fetchPendingActions();
+}, [fetchPendingActions]);47-54: Include collectionRef in the dependency array of useEffect
This is similar to a previous review comment.
The useEffect hook for subscribing to collection changes has an empty dependency array, which can cause issues if collectionRef changes. Include collectionRef.current in the dependencies to ensure proper subscription management.
Apply this diff:
useEffect(() => {
const unsubscribe = collectionRef.current.subscribe(({ items }) => {
setItems(items);
});
return () => {
unsubscribe();
};
-}, []);
+}, [collectionRef]);115-120: Improve accessibility for the delete button
This comment is a duplicate of a previous review comment.
To enhance accessibility, add the href, role, and aria-label attributes to the delete button. This ensures that assistive technologies can interpret the button correctly.
Apply this diff:
<a
onClick={() => deleteItem(item)}
className="removebutton"
+ href="#"
+ role="button"
+ aria-label={`Delete span ${item.id}`}
>
<i className="uk-icon-trash" />
</a>| const categoryPeriodsListRef = useRef([ | ||
| new CategoryPeriods({ category: "labaccess" }), | ||
| new CategoryPeriods({ category: "membership" }), | ||
| new CategoryPeriods({ category: "special_labaccess" }), | ||
| ]); | ||
|
|
||
| const categoryPeriodsList = categoryPeriodsListRef.current; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using useState instead of useRef for categoryPeriodsList
The current implementation using useRef for categoryPeriodsList creates mutable state outside of React's control. This could be contributing to the infinite network requests mentioned in the PR feedback. Consider using useState instead:
- const categoryPeriodsListRef = useRef([
- new CategoryPeriods({ category: "labaccess" }),
- new CategoryPeriods({ category: "membership" }),
- new CategoryPeriods({ category: "special_labaccess" }),
- ]);
-
- const categoryPeriodsList = categoryPeriodsListRef.current;
+ const [categoryPeriodsList] = useState(() => [
+ new CategoryPeriods({ category: "labaccess" }),
+ new CategoryPeriods({ category: "membership" }),
+ new CategoryPeriods({ category: "special_labaccess" }),
+ ]);Committable suggestion skipped: line range outside the PR's diff.
| // post({ | ||
| // url: `/webshop/member/${member_id}/ship_labaccess_orders`, | ||
| // expectedDataStatus: "ok", | ||
| // }); | ||
| post({ | ||
| url: `/webshop/member/${member_id}/ship_labaccess_orders`, | ||
| headers: { "Content-Type": "application/json" }, | ||
| payload: { | ||
| /* any required data */ | ||
| }, | ||
| expectedDataStatus: "ok", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Clean up commented code and improve error handling
- Remove the commented-out code block
- Add proper error handling for the POST request
- // post({
- // url: `/webshop/member/${member_id}/ship_labaccess_orders`,
- // expectedDataStatus: "ok",
- // });
post({
url: `/webshop/member/${member_id}/ship_labaccess_orders`,
headers: { "Content-Type": "application/json" },
payload: {
- /* any required data */
+ member_id,
+ spans: addSpans.map(s => s.id)
},
expectedDataStatus: "ok",
+ }).catch(error => {
+ console.error('Failed to ship labaccess orders:', error);
+ // Add appropriate error handling UI feedback
});Committable suggestion skipped: line range outside the PR's diff.
| useEffect(() => { | ||
| const unsubscribes = []; | ||
|
|
||
| // Subscribe to spans updates | ||
| unsubscribes.push( | ||
| spans.subscribe(({ items }) => { | ||
| categoryPeriodsList.forEach((periods) => | ||
| periods.replace(filterPeriods(items, periods.category)), | ||
| ); | ||
| }), | ||
| ); | ||
| this.categoryPeriodsList.forEach((cp) => { | ||
| this.unsubscribe.push( | ||
| cp.subscribe(() => | ||
| this.setState({ saveDisabled: !this.canSave() }), | ||
| ), | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| componentWillUnmount() { | ||
| this.unsubscribe.forEach((u) => u()); | ||
| } | ||
| // Subscribe to categoryPeriods updates | ||
| categoryPeriodsList.forEach((cp) => { | ||
| unsubscribes.push(cp.subscribe(() => setSaveDisabled(!canSave()))); | ||
| }); | ||
|
|
||
| render() { | ||
| const { showHistoric, saveDisabled } = this.state; | ||
| const { member_id, spans } = this.props; | ||
| return () => { | ||
| unsubscribes.forEach((u) => u()); | ||
| }; | ||
| }, [spans, categoryPeriodsList, canSave]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize useEffect dependencies and subscription management
The current implementation has several potential issues:
- Including
categoryPeriodsListin the dependency array could cause unnecessary effect reruns - The subscription setup could benefit from better error handling
useEffect(() => {
const unsubscribes = [];
+ let isSubscribed = true; // Add mounted check
// Subscribe to spans updates
unsubscribes.push(
spans.subscribe(({ items }) => {
+ if (!isSubscribed) return; // Prevent updates after unmount
categoryPeriodsList.forEach((periods) =>
periods.replace(filterPeriods(items, periods.category)),
);
}),
);
// Subscribe to categoryPeriods updates
categoryPeriodsList.forEach((cp) => {
+ if (!isSubscribed) return; // Prevent updates after unmount
unsubscribes.push(cp.subscribe(() => setSaveDisabled(!canSave())));
});
return () => {
+ isSubscribed = false;
unsubscribes.forEach((u) => u());
};
- }, [spans, categoryPeriodsList, canSave]);
+ }, [spans, canSave]); // Remove categoryPeriodsList from dependencies📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| const unsubscribes = []; | |
| // Subscribe to spans updates | |
| unsubscribes.push( | |
| spans.subscribe(({ items }) => { | |
| categoryPeriodsList.forEach((periods) => | |
| periods.replace(filterPeriods(items, periods.category)), | |
| ); | |
| }), | |
| ); | |
| this.categoryPeriodsList.forEach((cp) => { | |
| this.unsubscribe.push( | |
| cp.subscribe(() => | |
| this.setState({ saveDisabled: !this.canSave() }), | |
| ), | |
| ); | |
| }); | |
| } | |
| componentWillUnmount() { | |
| this.unsubscribe.forEach((u) => u()); | |
| } | |
| // Subscribe to categoryPeriods updates | |
| categoryPeriodsList.forEach((cp) => { | |
| unsubscribes.push(cp.subscribe(() => setSaveDisabled(!canSave()))); | |
| }); | |
| render() { | |
| const { showHistoric, saveDisabled } = this.state; | |
| const { member_id, spans } = this.props; | |
| return () => { | |
| unsubscribes.forEach((u) => u()); | |
| }; | |
| }, [spans, categoryPeriodsList, canSave]); | |
| useEffect(() => { | |
| const unsubscribes = []; | |
| let isSubscribed = true; // Add mounted check | |
| // Subscribe to spans updates | |
| unsubscribes.push( | |
| spans.subscribe(({ items }) => { | |
| if (!isSubscribed) return; // Prevent updates after unmount | |
| categoryPeriodsList.forEach((periods) => | |
| periods.replace(filterPeriods(items, periods.category)), | |
| ); | |
| }), | |
| ); | |
| // Subscribe to categoryPeriods updates | |
| categoryPeriodsList.forEach((cp) => { | |
| if (!isSubscribed) return; // Prevent updates after unmount | |
| unsubscribes.push(cp.subscribe(() => setSaveDisabled(!canSave()))); | |
| }); | |
| return () => { | |
| isSubscribed = false; | |
| unsubscribes.forEach((u) => u()); | |
| }; | |
| }, [spans, canSave]); // Remove categoryPeriodsList from dependencies |
| const sum_pending_labaccess_days = r.data.reduce( | ||
| (acc, value) => { | ||
| if (value.action.action === ADD_LABACCESS_DAYS) | ||
| return acc + value.action.value; | ||
| return acc; | ||
| }, | ||
| 0, | ||
| ); | ||
| setPendingLabaccessDays(sum_pending_labaccess_days); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Inconsistent variable naming convention
The variable sum_pending_labaccess_days uses snake_case, whereas the rest of the codebase predominantly uses camelCase. For consistency and readability, it's recommended to use camelCase for variable names in JavaScript.
Apply this diff to rename sum_pending_labaccess_days to sumPendingLabaccessDays:
const sumPendingLabaccessDays = r.data.reduce(
(acc, value) => {
if (value.action.action === ADD_LABACCESS_DAYS)
return acc + value.action.value;
return acc;
},
0,
);
-setPendingLabaccessDays(sum_pending_labaccess_days);
+setPendingLabaccessDays(sumPendingLabaccessDays);Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (1)
admin/src/Sales/OrderShow.js (1)
37-46: Optimize the subscription callback with useCallbackThe subscription callback should be memoized to prevent unnecessary re-renders and potential subscription churn.
Consider applying this optimization:
+ const handleOrderUpdate = useCallback(() => { + const { member_id } = order; + setMemberId(member_id); + }, [order]); useEffect(() => { - const unsubscribe = order.subscribe(() => { - const { member_id } = order; - setMemberId(member_id); - }); + const unsubscribe = order.subscribe(handleOrderUpdate); return () => { unsubscribe(); }; - }, [order]); + }, [order, handleOrderUpdate]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
admin/src/Sales/OrderShow.js(1 hunks)
🔇 Additional comments (4)
admin/src/Sales/OrderShow.js (4)
1-11: LGTM! Clean imports and component declaration
The conversion to a functional component follows React best practices with proper hook imports and clean prop destructuring.
54-56: Handle potential null value for memberId to prevent invalid links
If memberId is null or undefined when the component first renders, the <Link> component will generate an invalid URL.
79-79: Prevent potential division by zero when calculating item price
The price calculation could result in a division by zero error if item.count is zero.
61-89: Verify CollectionTable component integration
Let's verify that the CollectionTable component properly handles the collection props and row rendering.
Also applies to: 93-114
✅ Verification successful
CollectionTable integration is properly implemented
The implementation in OrderShow.js correctly uses the CollectionTable component, matching the expected props interface and usage patterns found across the codebase:
- The required props are properly provided:
collection={orderRows}- for data sourcecolumnsarray with correct structureemptyMessagefor empty staterowComponentwith correct render implementation
The component usage aligns with the standard patterns seen in 25+ other files across the codebase, and the table structure (headers, rows, currency formatting) follows the established conventions.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check CollectionTable implementation and usage patterns
# Find CollectionTable implementation
echo "Searching for CollectionTable implementation..."
ast-grep --pattern 'export $_ CollectionTable' -f js,jsx,ts,tsx
# Find other usages of CollectionTable for comparison
echo "Searching for CollectionTable usage patterns..."
rg -A 5 'CollectionTable' --type js --type jsx --type tsx
Length of output: 539
Script:
#!/bin/bash
# Let's try a different approach to find CollectionTable implementation and usage
# Find CollectionTable implementation
echo "Searching for CollectionTable implementation..."
ast-grep --pattern 'const CollectionTable = $_'
# Find CollectionTable usage with ripgrep
echo "Searching for CollectionTable usage patterns..."
rg "CollectionTable" -A 5
# Find potential files containing CollectionTable
echo "Finding files that might contain CollectionTable..."
fd "CollectionTable" --type f
Length of output: 48464
| rowComponent={({ item }) => ( | ||
| <tr> | ||
| <td> | ||
| <Link to={`/sales/product/${item.product_id}`}> | ||
| {item.name} | ||
| </Link> | ||
| </td> | ||
| <td className="uk-text-right"> | ||
| <Currency | ||
| value={(100 * item.amount) / item.count} | ||
| />{" "} | ||
| kr | ||
| </td> | ||
| <td>{item.count}</td> | ||
| <td className="uk-text-right"> | ||
| <Currency value={100 * item.amount} /> kr | ||
| </td> | ||
| </tr> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add key props to table row components
The row components passed to CollectionTable should have key props for optimal rendering performance.
Apply these changes:
- rowComponent={({ item }) => (
+ rowComponent={({ item, key }) => (
- <tr>
+ <tr key={key}>This change should be applied to both CollectionTable instances.
Also applies to: 102-113
fix gift card network error
fix span show
fix messages
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Outside diff range and nitpick comments (3)
admin/src/Messages/MessageAdd.js (2)
14-19: Add error handling and consider i18nThe save operation lacks error handling and uses hardcoded Swedish text. Consider:
- Adding error handling
- Implementing internationalization for messages
const onSend = () => { message.save().then(() => { router.push("/messages"); notifySuccess("Ditt meddelande har skickats"); - }); + }) + .catch((error) => { + notifyError("Failed to send message"); + console.error("Failed to save message:", error); + }); };
21-30: Add prop-types and document MessageForm propsThe component would benefit from proper prop-types validation and documentation of the MessageForm props interface.
+import PropTypes from 'prop-types'; + function MessageAdd(props) { // ... component logic ... } + +MessageAdd.propTypes = { + router: PropTypes.shape({ + push: PropTypes.func.isRequired, + }).isRequired, +}; + +// Document MessageForm props interface +/** + * @typedef {Object} MessageFormProps + * @property {boolean} recipientSelect - Whether to show recipient selection + * @property {Message} message - Message instance to edit + * @property {Function} onSave - Callback when save is requested + */admin/src/Messages/MessageShow.js (1)
27-55: Consider optimizing the update handlerThe update handler could be memoized to prevent unnecessary recreations and potential race conditions.
Consider this optimization:
+ const updateMessage = useMemo(() => () => { - const updateMessage = () => { setMessage({ body: messageInstance.body, // ... other fields }); - }; + }, [messageInstance]); useEffect(() => { - // Call the function once to initialize state updateMessage(); const unsubscribe = messageInstance.subscribe(updateMessage); return () => unsubscribe(); }, [messageInstance, updateMessage]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
admin/src/Membership/SpanShow.jsx(1 hunks)admin/src/Messages/MessageAdd.js(1 hunks)admin/src/Messages/MessageShow.js(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
admin/src/Messages/MessageShow.js
[error] 96-96: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
[error] 100-100: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🔇 Additional comments (7)
admin/src/Messages/MessageAdd.js (1)
14-19: Add cleanup for async operations
The component should handle potential unmounting during the save operation to prevent memory leaks or state updates on unmounted components.
admin/src/Messages/MessageShow.js (4)
1-5: LGTM! Clean imports setup
The imports are well-organized and include all necessary dependencies for the functional component implementation.
93-101:
Avoid using dangerouslySetInnerHTML to prevent XSS vulnerabilities.
Using dangerouslySetInnerHTML without sanitization exposes the application to cross-site scripting (XSS) attacks.
The previous review's suggestion to use DOMPurify is still valid and should be implemented to secure the application.
🧰 Tools
🪛 Biome (1.9.4)
[error] 96-96: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
[error] 100-100: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
48-54: Verify subscription cleanup across converted components
Given the reported issues with infinite network requests in various pages, ensure that subscription cleanup is working correctly.
Let's check the subscription cleanup pattern across converted components:
✅ Verification successful
Based on the comprehensive search results, I can now generate the final response:
Subscription cleanup is properly implemented across components
The codebase shows consistent and correct implementation of subscription cleanup:
- All functional components properly use
useEffectwith cleanup functions - Class components correctly implement
componentWillUnmount - The subscription cleanup pattern follows React best practices:
- Subscriptions are created in
useEffect/componentDidMount - Cleanup functions are properly returned/implemented
- Unsubscribe functions are called in cleanup
- Dependencies are correctly specified in the dependency arrays
- Subscriptions are created in
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for subscription cleanup patterns in converted components
rg -A 5 'subscribe.*\(' admin/src/
# Look for useEffect cleanup
ast-grep --pattern 'useEffect(() => {
$$$
return () => {
$$$
};
}, [$$$]);'
Length of output: 51302
6-25: 🛠️ Refactor suggestion
Add error handling for message retrieval
The component should handle cases where the message retrieval fails or the message doesn't exist. Consider adding error state and handling.
Let's check if there are any error boundaries or error handling patterns in the codebase:
Consider implementing error handling:
function MessageShow(props) {
const { id } = props.match.params;
+ const [error, setError] = useState(null);
const messageInstance = useMemo(() => {
+ try {
return Message.get(id);
+ } catch (err) {
+ setError(err);
+ return null;
+ }
}, [id]);admin/src/Membership/SpanShow.jsx (2)
10-18:
Fix infinite requests and add error handling
The current implementation could cause infinite network requests and doesn't handle errors properly.
useEffect(() => {
+ let isSubscribed = true;
+ setIsLoading(true);
+
+ // Initial data fetch
+ spanInstance.fetch().catch(error => {
+ if (isSubscribed) {
+ setError(error);
+ setIsLoading(false);
+ }
+ });
+
const unsubscribe = spanInstance.subscribe(() => {
+ if (!isSubscribed) return;
try {
setData(spanInstance.saved);
+ setIsLoading(false);
+ setError(null);
} catch (error) {
+ setError(error);
+ console.error('Error updating span data:', error);
}
});
return () => {
+ isSubscribed = false;
unsubscribe();
};
-}, [spanInstance]);
+}, [spanInstance, spanInstance.saved]);1-32: Verify related components and models
Let's check for any related components or models that might affect the SpanShow component's behavior.
✅ Verification successful
The subscription pattern in SpanShow is correctly implemented
The subscription pattern used in SpanShow.jsx follows the same pattern as seen in other components (MemberBoxSpans, SpanList) and properly handles:
- Subscription setup using useEffect
- Cleanup through the unsubscribe function
- Proper dependency tracking with spanInstance
- Consistent state management with useState
The Span model is correctly imported and used, and the component is properly integrated into the routing system. No issues were found in the implementation.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for related components and models
# Expected: Find components that might affect SpanShow
# Look for components importing SpanShow
rg -l "import.*SpanShow"
# Look for components using the Span model
rg -l "import.*Span.*from.*Models/Span"
# Check for any similar subscription patterns that might have the same issues
ast-grep --pattern 'useEffect(() => {
$$$
subscribe($$$)
$$$
})'
Length of output: 368
Script:
#!/bin/bash
# Let's examine the subscription patterns in the related files
# Check SpanList.jsx for similar subscription patterns
rg "subscribe" admin/src/Membership/SpanList.jsx -B 2 -A 2
# Check MemberBoxSpans.jsx for similar patterns
rg "subscribe" admin/src/Membership/MemberBoxSpans.jsx -B 2 -A 2
# Check the Span model implementation
rg "subscribe" admin/src/Models/Span.js -B 2 -A 2
# Look at the Routes.jsx to see how SpanShow is used
cat admin/src/Membership/Routes.jsx
Length of output: 4383
Script:
#!/bin/bash
# Let's check the Span model implementation and its usage pattern
cat admin/src/Models/Span.js
# Also check MemberBoxSpans to understand the complete usage context
cat admin/src/Membership/MemberBoxSpans.jsx
# And SpanList to see the list implementation
cat admin/src/Membership/SpanList.jsx
Length of output: 13061
| import React, { useMemo } from "react"; | ||
| import { withRouter } from "react-router"; | ||
| import MessageForm from "../Components/MessageForm"; | ||
| import Message from "../Models/Message"; | ||
| import { notifySuccess } from "../message"; | ||
| import { withRouter } from "react-router"; | ||
|
|
||
| class MessageAdd extends React.Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.message = new Message(); | ||
| } | ||
| function MessageAdd(props) { | ||
| const { router } = props; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider upgrading to modern React Router
The use of withRouter HOC and router prop is deprecated in newer versions of React Router. Consider upgrading to use the useNavigate hook instead.
-import { withRouter } from "react-router";
+import { useNavigate } from "react-router-dom";
-function MessageAdd(props) {
- const { router } = props;
+function MessageAdd() {
+ const navigate = useNavigate();Committable suggestion skipped: line range outside the PR's diff.
| // Create a new Message instance | ||
| const message = useMemo(() => new Message(), []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Replace useMemo with useState for mutable state
The useMemo hook is unnecessary here as the Message instance is mutable state. Use useState instead to properly manage the message state and its updates.
-// Create a new Message instance
-const message = useMemo(() => new Message(), []);
+const [message, setMessage] = useState(() => new Message());📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Create a new Message instance | |
| const message = useMemo(() => new Message(), []); | |
| const [message, setMessage] = useState(() => new Message()); |
| return ( | ||
| <div> | ||
| <h2>Medlemsperiod {data.span_id}</h2> | ||
| <dl className="uk-description-list"> | ||
| {_.keys(data).map((key) => ( | ||
| <div key={key}> | ||
| <dt>{key}:</dt> | ||
| <dd>{data[key]}</dd> | ||
| </div> | ||
| ))} | ||
| </dl> | ||
| </div> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add data validation and field formatting
The current implementation doesn't validate data or format fields properly, which could cause rendering issues.
return (
<div>
- <h2>Medlemsperiod {data.span_id}</h2>
+ <h2>Medlemsperiod {data?.span_id || 'N/A'}</h2>
<dl className="uk-description-list">
- {_.keys(data).map((key) => (
+ {Object.entries(data || {}).map(([key, value]) => (
<div key={key}>
<dt>{key}:</dt>
- <dd>{data[key]}</dd>
+ <dd>{value?.toString() || 'N/A'}</dd>
</div>
))}
</dl>
</div>
);Also, consider adding PropTypes for type checking:
import PropTypes from 'prop-types';
SpanShow.propTypes = {
match: PropTypes.shape({
params: PropTypes.shape({
span_id: PropTypes.string.isRequired
}).isRequired
}).isRequired
};| const { span_id } = props.match.params; | ||
| const spanInstance = useMemo(() => Span.get(span_id), [span_id]); | ||
| const [data, setData] = useState({}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initialize data state with loading indicator
The empty initial state {} could cause the blank page issue. Consider adding a loading state and error handling.
-const [data, setData] = useState({});
+const [data, setData] = useState(null);
+const [isLoading, setIsLoading] = useState(true);
+const [error, setError] = useState(null);
+if (isLoading) return <div>Loading...</div>;
+if (error) return <div>Error: {error.message}</div>;
+if (!data) return null;Committable suggestion skipped: line range outside the PR's diff.
| import React, { useEffect, useMemo, useState } from "react"; | ||
| import * as _ from "underscore"; | ||
| import Span from "../Models/Span"; | ||
|
|
||
| class SpanShow extends React.Component { | ||
| constructor(props) { | ||
| super(props); | ||
| const { span_id } = props.match.params; | ||
| this.span = Span.get(span_id); | ||
| this.state = { data: {} }; | ||
| } | ||
|
|
||
| componentDidMount() { | ||
| this.unsubscribe = this.span.subscribe(() => | ||
| this.setState({ data: this.span.saved }), | ||
| ); | ||
| } | ||
| function SpanShow(props) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Missing error handling and incorrect Base model usage causing infinite requests
The verification reveals several critical issues that explain the reported regressions:
- The Span model extends Base but doesn't implement proper subscription/data fetching methods. The
get()method used in SpanShow is inherited from Base but not visible in the implementation. - SpanShow component uses
spanInstance.savedwithout checking if the data is actually loaded or if there are any errors. - No error handling exists in the subscription effect, which could lead to unhandled promise rejections.
- The component doesn't handle loading states, potentially causing the blank page issue.
Key locations needing attention:
admin/src/Models/Span.js: Missing proper data fetching implementationadmin/src/Membership/SpanShow.jsx: Missing error handling and loading states- Need to verify Base model implementation for potential subscription issues
🔗 Analysis chain
Verify the Span model import path and implementation
Let's verify the Span model implementation to ensure it properly handles subscriptions and data fetching.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check the Span model implementation
# Expected: Find the Span model class with proper subscription and data fetching logic
# Look for the Span model implementation
ast-grep --pattern 'class Span {
$$$
get($_) {
$$$
}
$$$
subscribe($_) {
$$$
}
$$$
}'
# Check for any references to span.saved
rg -A 5 'span\.saved'
Length of output: 133
Script:
#!/bin/bash
# Let's try a broader search for the Span model and its usage
# Find the Span model file
fd "Span.js" -t f
# Look for class definition with a more relaxed pattern
ast-grep --pattern 'class Span'
# Search for Span-related imports
rg "import.*Span.*from"
# Search for Span usage patterns
rg "new Span\(" -A 3
Length of output: 1478
Script:
#!/bin/bash
# Let's examine the Span model implementation and its usage in SpanShow
# Check the Span model implementation
cat admin/src/Models/Span.js
# Look for subscription or data fetching patterns in SpanShow
cat admin/src/Membership/SpanShow.jsx
# Search for any error handling patterns in SpanShow
rg "catch|error" admin/src/Membership/SpanShow.jsx
Length of output: 6554
emanuelen5
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good! I've reviewed about halfway. I had some comments.
| period.subscribe(() => { | ||
| this.setState({ start: period.start, end: period.end }); | ||
| useEffect(() => { | ||
| // Synchronize state with period on subscription updates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove comment
| componentWillUnmount() { | ||
| this.unsubscribe(); | ||
| } | ||
| // useEffect to handle subscription |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove comment
| this.setState({ page, items, loading: false }), | ||
| ); | ||
| } | ||
| // Destructure props |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove all comments in this file
admin/src/Membership/KeyEdit.jsx
Outdated
| model={keyRef.current} | ||
| name="created_at" | ||
| title="Skapad" | ||
| disabled={true} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
disabled does not exist on the DateTimeInput class, you can remove it here and below
| </div> | ||
| ); | ||
| if (!group) { | ||
| return <div>Group not found</div>; // Handle missing context gracefully |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove comment
| useEffect(() => { | ||
| get({ url: "/membership/permission" }).then((data) => { | ||
| this.options = data.data; | ||
| this.setState({ showOptions: this.filterOptions() }); | ||
| const fetchedOptions = data.data; | ||
| setOptions(fetchedOptions); | ||
| setShowOptions(filterOptions(fetchedOptions)); | ||
| }); | ||
| } | ||
|
|
||
| componentDidMount() { | ||
| this.unsubscribe = this.collection.subscribe(() => | ||
| this.setState({ showOptions: this.filterOptions() }), | ||
| ); | ||
| } | ||
| const unsubscribe = collection.subscribe(() => { | ||
| setShowOptions(filterOptions(options)); | ||
| }); | ||
|
|
||
| componentWillUnmount() { | ||
| this.unsubscribe(); | ||
| } | ||
| return () => { | ||
| unsubscribe(); | ||
| }; | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any comment on this? I can't see any problem on http://localhost:8009/membership/groups/1/permissions, but perhaps the Rabbit has a point?
admin/src/Membership/KeyEdit.jsx
Outdated
| import React from "react"; | ||
| import Key from "../Models/Key"; | ||
| import React, { useCallback, useEffect, useRef, useState } from "react"; | ||
| import { useParams, withRouter } from "react-router"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any comment on this? I think the Rabbit might be correct here as well. In that case, you can remove the withRouter import and its usage on line 107
admin/src/Membership/KeyEdit.jsx
Outdated
| ); | ||
| } | ||
|
|
||
| export default withRouter(KeyEdit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can remove withRouter here as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I stopped reviewing here. Will continue later
All of these regressions seem fixed now, thank you! |
|
Thank you so much for your detailed feedback, will address these issues later. |
refactored the 'preventDefault' error, and removed comments
|
Hi, sorry for the delay in response, I've updated the code according to your feedback.
I have not address these two comments:
Thanks again! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🧹 Outside diff range and nitpick comments (17)
admin/src/Components/RequestPasswordReset.jsx (2)
14-29: Consider improving error handling and message consistency.Two suggestions for improvement:
- The error message is in English while the UI is in Swedish. Consider translating it to maintain consistency.
- Add error handling for the API call to handle potential failures.
Consider updating the code as follows:
const submit = (e) => { e.preventDefault(); if (!userIdentification) { - showError("You need to fill your email or member number."); + showError("Du måste fylla i din e-post eller ditt medlemsnummer."); return; } - auth.requestPasswordReset(userIdentification).then(() => { - showSuccess( - "Link to password reset will be sent to your email shortly.", - ); - browserHistory.push("/"); - }); + auth.requestPasswordReset(userIdentification) + .then(() => { + showSuccess( + "En länk för att återställa lösenordet kommer att skickas till din e-post.", + ); + browserHistory.push("/"); + }) + .catch((error) => { + showError("Det gick inte att skicka återställningslänken. Försök igen senare."); + }); };
31-79: Consider adding accessibility improvements.The form structure is clean, but could benefit from accessibility enhancements.
Consider these improvements:
<div className="uk-form-row"> <div className="uk-form-icon"> <i className="uk-icon-user" /> <input + aria-label="Email eller medlemsnummer" value={userIdentification} onChange={handleInputChange} className="uk-form-large uk-form-width-large" type="text" placeholder="Email/Medlemsnummer" autoComplete="username" /> </div> </div>admin/src/Components/Textarea.jsx (1)
9-22: Consider optimizing the useEffect dependenciesThe current implementation re-creates the subscription whenever
props.modelorprops.namechanges. Consider destructuring these values outside useEffect to avoid unnecessary re-subscriptions.- useEffect(() => { - const { model, name } = props; + const { model, name } = props; + useEffect(() => { const handleModelChange = () => { setValue(model[name] === null ? "" : model[name]); setIsDirty(model.isDirty(name)); }; const unsubscribe = model.subscribe(handleModelChange); + handleModelChange(); // Initialize the state return () => { unsubscribe(); }; - }, [props.model, props.name]); + }, [model, name]);admin/src/Components/MessageForm.jsx (2)
Line range hint
49-74: Optimize loadOptions function with useCallbackThe
loadOptionsfunction is recreated on every render. Consider memoizing it with useCallback to improve performance, especially since it's passed as a prop to the Async component.+ const loadOptions = useCallback((inputValue, callback) => { - const loadOptions = (inputValue, callback) => { Promise.all([ // ... existing code ... ]); - }; + }, []);
98-101: Simplify recipients onChange handlerThe current implementation updates both the model and local state. Since the model subscription will trigger a state update, the manual state update is redundant.
onChange={(values) => { message.recipients = values; - setRecipients(values); }}admin/src/Components/MemberForm.js (1)
73-78: Reconsider number type for zipcode fieldUsing
type="number"for zipcodes can be problematic as it:
- Removes leading zeros
- Prevents entering spaces or dashes
- Shows spinner controls which are unnecessary for postal codes
- type="number" + type="text" + pattern="[0-9 -]*"admin/src/Components/CollectionTable.js (4)
Line range hint
71-115: Improve pagination accessibility and performanceThe pagination implementation has room for improvements:
- Missing ARIA labels for better accessibility
- Inline function creation in onClick could cause unnecessary re-renders
- Missing keyboard navigation support
Consider these improvements:
<ul className="uk-pagination" style={{ clear: "both" }}> {_.range(1, page.count + 1).map((i) => { const distance = Math.abs(i - page.index); if (distance === 0) { return ( - <li key={i} className="uk-active"> - <span>{i}</span> + <li key={i} className="uk-active" aria-current="page"> + <span aria-label={`Current Page, Page ${i}`}>{i}</span> </li> ); } else if ( distance <= show_count || i === 1 || i === page.count ) { return ( <li key={i}> <a + role="button" + tabIndex={0} + aria-label={`Go to Page ${i}`} onClick={() => { setLoading(true); if (onPageNav) onPageNav(i); }} + onKeyPress={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + setLoading(true); + if (onPageNav) onPageNav(i); + } + }} > {i} </a> </li> ); } })} </ul>
117-124: Consider adding user feedback for delete operationsWhile the delete operation handles the basic flow, it would be beneficial to provide feedback to users about the success or failure of the operation.
Consider adding a toast notification:
const deleteItem = (collection, item) => { return confirmModal(item.deleteConfirmMessage()) .then(() => item.del()) .then( - () => collection.fetch(), + () => { + collection.fetch(); + // Assuming you have a notification system + props.showNotification?.({ + message: 'Item successfully deleted', + type: 'success' + }); + }, - () => null, + (error) => { + props.showNotification?.({ + message: 'Failed to delete item', + type: 'error' + }); + return null; + } ); };
157-167: Enhance table accessibilityThe table is missing important accessibility attributes that would make it more usable for screen readers.
Consider adding these accessibility improvements:
<table className={ "uk-table uk-table-condensed uk-table-striped uk-table-hover" + (loading ? " backboneTableLoading" : "") } + role="grid" + aria-busy={loading} + aria-label="Collection Data Table" > - <thead> + <thead role="rowgroup"> - <tr>{headers}</tr> + <tr role="row">{headers}</tr> </thead> - <tbody>{rows}</tbody> + <tbody role="rowgroup">{rows}</tbody> </table>
172-173: Internationalize loading messageThe loading message is hardcoded in Swedish. Consider using an internationalization system for better maintainability and localization support.
-<i className="uk-icon-refresh uk-icon-spin" /> Hämtar data... +<i className="uk-icon-refresh uk-icon-spin" /> {t('common.loading')}admin/src/Components/FileInput.jsx (1)
40-40: Add PropTypes for better development experienceConsider adding PropTypes to document and validate the component's props.
+import PropTypes from 'prop-types'; const FileInput = ({ onFileSelect, acceptedFileTypes, maxFileSize }) => { // ... component implementation }; +FileInput.propTypes = { + onFileSelect: PropTypes.func, + acceptedFileTypes: PropTypes.string, + maxFileSize: PropTypes.number +}; export default FileInput;admin/src/Membership/KeyEdit.jsx (1)
1-105: Well-executed component conversion with room for enhancementThe conversion from class to function component is well done, properly utilizing React hooks and maintaining the original functionality. Consider these architectural improvements for future iterations:
- Add a custom hook for key management to encapsulate the subscription logic
- Implement proper error boundaries for error handling
- Consider adding loading states for async operations
admin/src/Components/DatePeriodInput.js (2)
1-5: Add PropTypes validation for the period prop.Consider adding PropTypes to validate the shape and required properties of the
periodprop, which would help catch potential issues during development.import React, { useEffect, useState } from "react"; import DayPickerInput from "react-day-picker/DayPickerInput"; import { formatUtcDate, parseUtcDate, utcToday } from "../utils"; +import PropTypes from 'prop-types'; const DatePeriodInput = ({ period }) => {Add at the bottom:
+DatePeriodInput.propTypes = { + period: PropTypes.shape({ + start: PropTypes.instanceOf(Date), + end: PropTypes.instanceOf(Date), + subscribe: PropTypes.func.isRequired, + isDirty: PropTypes.func.isRequired, + isValid: PropTypes.func.isRequired + }).isRequired +};
31-74: Enhance accessibility and reduce code duplication.
- The DayPickerInput components share similar props and structure. Consider extracting a reusable component.
- Add proper ARIA labels for better accessibility.
+const DateInput = ({ value, onChange, isDirty, isValid, isHistoric }) => ( + <span className={isDirty ? "ma-changed" : ""}> + <DayPickerInput + inputProps={{ + size: 10, + className: "uk-input" + (isValid ? "" : " uk-form-danger"), + style: { + marginTop: "2px", + ...(isHistoric && historicStyle), + }, + "aria-label": `${isDirty ? "Modified " : ""}date input` + }} + placeholder="YYYY-MM-DD" + value={value || ""} + parseDate={parseUtcDate} + formatDate={formatUtcDate} + onDayChange={onChange} + /> + </span> +); return ( <span> - {/* Input for start date */} - <span className={period.isDirty("start") ? "ma-changed" : ""}> - <DayPickerInput - inputProps={{...}} - placeholder="YYYY-MM-DD" - value={start || ""} - parseDate={parseUtcDate} - formatDate={formatUtcDate} - onDayChange={(date) => handleDayChange(date, "start")} - /> - </span> + <DateInput + value={start} + onChange={(date) => handleDayChange(date, "start")} + isDirty={period.isDirty("start")} + isValid={period.isValid("start")} + isHistoric={start && start < today} + /> - - {/* Input for end date */} - <span className={period.isDirty("end") ? "ma-changed" : ""}> - <DayPickerInput {...similar props} /> - </span> + <DateInput + value={end} + onChange={(date) => handleDayChange(date, "end")} + isDirty={period.isDirty("end")} + isValid={period.isValid("end")} + isHistoric={end && end < today} + /> </span> );admin/src/Components/SelectInput.jsx (1)
11-22: Consider enhancing data fetching with abort controllerWhile the data fetching logic is correct, it could benefit from proper cleanup and error handling.
Consider applying this improvement:
useEffect(() => { + const controller = new AbortController(); if (props.dataSource) { let newOptions = []; if (props.nullOption) { newOptions = [props.nullOption]; } - get({ url: props.dataSource, params: { page_size: 0 } }).then( + get({ + url: props.dataSource, + params: { page_size: 0 }, + signal: controller.signal + }).then( (data) => setOptions(newOptions.concat(data.data)), - () => null, + (error) => { + if (!error.name === 'AbortError') { + console.error('Failed to fetch options:', error); + } + } ); } + return () => controller.abort(); }, [props.dataSource, props.nullOption]);admin/src/Components/ProductForm.jsx (2)
96-99: Simplify form submission handlingThe current implementation uses both
return falseande.preventDefault()which is redundant.Choose one approach:
onSubmit={(e) => { - onSave(); - return false; + e.preventDefault(); + onSave(); }}Or:
onSubmit={(e) => { onSave(); return false; }}
101-259: Consider extracting common fieldset patternsThe form has multiple fieldsets with similar structures. Consider extracting a reusable
FormSectioncomponent to reduce repetition and improve maintainability.Example implementation:
const FormSection = ({ icon, title, children }) => ( <fieldset className="uk-margin-top"> <legend> <i className={`uk-icon-${icon}`} /> {title} </legend> {children} </fieldset> );Usage:
<FormSection icon="shopping-cart" title="Produkt"> <TextInput model={product} name="name" title="Produktnamn" /> {/* ... other inputs */} </FormSection>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
admin/src/Components/CollectionTable.js(5 hunks)admin/src/Components/DatePeriodInput.js(1 hunks)admin/src/Components/FileInput.jsx(1 hunks)admin/src/Components/MemberForm.js(1 hunks)admin/src/Components/MessageForm.jsx(3 hunks)admin/src/Components/ProductForm.jsx(1 hunks)admin/src/Components/RequestPasswordReset.jsx(1 hunks)admin/src/Components/SearchBox.jsx(1 hunks)admin/src/Components/SelectInput.jsx(1 hunks)admin/src/Components/TextInput.jsx(1 hunks)admin/src/Components/Textarea.jsx(1 hunks)admin/src/Membership/GroupBoxEditInfo.jsx(1 hunks)admin/src/Membership/KeyEdit.jsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- admin/src/Components/SearchBox.jsx
- admin/src/Membership/GroupBoxEditInfo.jsx
🧰 Additional context used
📓 Learnings (1)
admin/src/Membership/KeyEdit.jsx (1)
Learnt from: emanuelen5
PR: makerspace/makeradmin#581
File: admin/src/Membership/KeyEdit.jsx:2-2
Timestamp: 2024-12-09T22:05:34.237Z
Learning: In React components using React Router v6, if hooks like `useParams` are used, the `withRouter` higher-order component is unnecessary and should be removed to simplify the code.
🪛 Biome (1.9.4)
admin/src/Components/Textarea.jsx
[error] 52-52: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
admin/src/Components/TextInput.jsx
[error] 56-57: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
admin/src/Components/ProductForm.jsx
[error] 35-35: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
admin/src/Components/SelectInput.jsx
[error] 52-52: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
🔇 Additional comments (23)
admin/src/Components/RequestPasswordReset.jsx (2)
1-7: LGTM! Clean imports and modern component declaration.
The component has been properly converted to a functional component with appropriate imports.
8-12: LGTM! Clean state management implementation.
The component has been properly converted to use controlled input with useState.
admin/src/Components/Textarea.jsx (1)
52-52: 🛠️ Refactor suggestion
Refactor the onChange handler for better maintainability
The current implementation directly assigns to the model within an expression, which can be confusing and harder to maintain.
- onChange={(e) => (model[name] = e.target.value)}
+ onChange={(e) => {
+ model[name] = e.target.value;
+ handleModelChange();
+ }}🧰 Tools
🪛 Biome (1.9.4)
[error] 52-52: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
admin/src/Components/TextInput.jsx (1)
55-58: 🛠️ Refactor suggestion
Simplify the onChange handler and add model update trigger
The current implementation has a complex nested structure and lacks a model update trigger.
- onChange={(event) =>
- (model[name] =
- event.target.value === "" ? null : event.target.value)
- }
+ onChange={(event) => {
+ const newValue = event.target.value === "" ? null : event.target.value;
+ model[name] = newValue;
+ handleModelChange();
+ }}🧰 Tools
🪛 Biome (1.9.4)
[error] 56-57: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
admin/src/Components/MemberForm.js (3)
1-18: LGTM! Clean hooks implementation
The conversion to hooks is well-implemented with proper cleanup and dependency management.
24-27: 🛠️ Refactor suggestion
Modernize form submission handling
The current approach of returning false is an older pattern. Modern React code should explicitly prevent the default form action.
- onSave();
- return false;
+ e.preventDefault();
+ onSave();Likely invalid or redundant comment.
10-13: Verify subscription behavior to prevent infinite renders
Given the reported regressions in the PR with infinite network requests, let's verify the member subscription implementation.
admin/src/Components/FileInput.jsx (2)
6-8:
Implement file upload handlers
The component is missing essential functions for handling file selection and upload.
const clearUpload = () => {
setFilename("");
+ setUploadProgress(0);
+ setUploadStatus('idle');
+ setErrorMessage('');
};
+const handleFileSelect = (event) => {
+ const file = event.target.files[0];
+ if (file) {
+ setFilename(file.name);
+ onFileSelect?.(file);
+ }
+};Likely invalid or redundant comment.
1-40: Verify component usage patterns
Let's check how this component is used across the codebase to ensure our changes are compatible with existing implementations.
admin/src/Membership/KeyEdit.jsx (6)
1-9: LGTM! Imports correctly updated for functional component
The imports have been properly updated to include necessary hooks and the direct import of useParams without withRouter, aligning with React Router v6 best practices.
52-63: Consider renaming DateTimeInput to DateTimeDisplay
Based on previous feedback, DateTimeInput is actually a readonly component.
Consider creating a new issue to rename this component to DateTimeDisplay since it's readonly. This would make the component's purpose clearer.
105-105: LGTM! Clean export without HOC
The export statement has been correctly updated to remove the withRouter HOC, aligning with modern React Router practices.
24-26: 🛠️ Refactor suggestion
Add error handling to onSave
The onSave function should include error handling similar to onDelete.
const onSave = useCallback(() => {
- keyRef.current.save();
+ keyRef.current.save()
+ .then(() => {
+ // Consider showing a success message
+ })
+ .catch((error) => {
+ // Handle save errors
+ console.error('Failed to save key:', error);
+ });
}, []);15-22: Verify subscription cleanup implementation
The subscription setup and cleanup look correct. Let's verify the unsubscribe implementation in the Key model.
✅ Verification successful
Based on the search results, I can see a clear pattern of subscription cleanup implementation across the codebase. The subscribe method is defined in admin/src/Models/Base.js which returns an unsubscribe function that removes the subscriber from the subscribers list. This pattern is consistently used throughout the codebase, including in the file under review.
Let's verify the implementation in the Base model to ensure the cleanup is properly handled:
Let me try a different approach to get the Base model implementation:
Subscription cleanup implementation is correct
The subscription cleanup in the KeyEdit.jsx component is properly implemented. The Base model's subscribe method returns a cleanup function that removes the subscriber from the list, and the component correctly uses this cleanup function in the useEffect's return callback. This pattern is consistently used across the codebase and follows React's best practices for cleanup.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for subscribe/unsubscribe implementation
rg -A 5 "subscribe\(|unsubscribe\("
Length of output: 25249
Script:
#!/bin/bash
# Get the Base model implementation
ast-grep --pattern 'subscribe(callback) {
$$$
}'
Length of output: 52
Script:
#!/bin/bash
# Get the Base model implementation with more context
rg -A 10 "subscribe\(callback\)" admin/src/Models/Base.js
Length of output: 452
10-13: Verify the Key instance lifecycle management
The Key instance is stored in a ref and initialized only once. Let's verify that this doesn't cause any issues with updates or re-renders.
✅ Verification successful
Let me gather more information about the Base model and any potential update mechanisms.
Let me try one more search with corrected patterns:
Based on the gathered information, I can now provide the final response:
The Key instance ref usage is implemented correctly
The implementation is safe because:
- The Key instance is properly initialized using
Key.get()which fetches fresh data viarefresh() - The component subscribes to Key model changes in useEffect and properly cleans up the subscription
- The Base model (parent of Key) has proper refresh and update mechanisms
- The ref is used to maintain a stable reference to the Key instance across re-renders while allowing the instance itself to be mutable
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for other components using Key.get to understand the pattern
ast-grep --pattern 'Key.get($_)'
# Search for Key model implementation
rg -A 10 "class Key"
Length of output: 2121
Script:
#!/bin/bash
# Search for Base model implementation
rg -A 10 "class Base extends|class Base {"
# Look for any update or refresh methods in Key or Base
ast-grep --pattern 'update($$$)'
# Search for Key.get implementation
ast-grep --pattern 'get($_) {
$$$
}'
# Look for useEffect usage with Key
rg -A 5 "useEffect.*Key"
Length of output: 397
Script:
#!/bin/bash
# Search for Base model implementation with fixed pattern
rg -A 10 "^class Base"
# Look for model update patterns in the specific files
rg -A 5 "update|refresh" admin/src/Models/
# Search for effects in KeyEdit
rg -A 5 "useEffect" admin/src/Membership/KeyEdit.jsx
# Look for Key.get static method
rg -A 5 "static get" admin/src/Models/Key.js
Length of output: 9481
admin/src/Components/DatePeriodInput.js (2)
6-16: LGTM! Clean implementation of state and effects.
The implementation correctly:
- Initializes state with period values
- Sets up subscription with proper cleanup
- Uses appropriate dependency array
9-16: Verify subscription handling to prevent infinite requests.
Given the reported regressions in the PR comments about infinite network requests, we should verify that the subscription handling in this component isn't contributing to the issue.
admin/src/Components/SelectInput.jsx (3)
2-9: LGTM! Clean conversion to function component
The component setup follows React best practices with proper hooks usage and state initialization.
24-37: LGTM! Well-implemented subscription management
The subscription setup and cleanup are properly handled with appropriate dependencies.
52-52:
Avoid assignment within an expression in the onChange handler
The assignment within the expression can lead to confusion and side effects.
Apply this improvement:
-onChange={(o) => (props.model[props.name] = props.getValue(o))}
+onChange={(o) => {
+ props.model[props.name] = props.getValue(o);
+}}🧰 Tools
🪛 Biome (1.9.4)
[error] 52-52: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
admin/src/Components/ProductForm.jsx (3)
12-22: LGTM! Well-structured utility functions
The utility functions are well-implemented with:
- Modern array methods replacing lodash dependencies
- Proper null checks and fallback logic
- Clear single responsibilities
53-63: LGTM! Well-implemented event handlers
The handlers are concise, focused, and include proper checks before mutations.
34-37:
Fix variable usage before declaration
The variable selectedActionType is being used before it's declared.
Apply this fix:
- const selectedActionType = filterSelectedActionType(
+ const newSelectedActionType = filterSelectedActionType(
selectedActionType,
newAvailableActionTypes,
);🧰 Tools
🪛 Biome (1.9.4)
[error] 35-35: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
| model={message} | ||
| name="subject" | ||
| title="Ärende" | ||
| onChange={() => setBodyLength(message.body.length)} // Ensure the length is updated when body changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consolidate duplicate onChange handlers
The same onChange handler is duplicated for both TextInput and Textarea components.
+ const handleBodyChange = useCallback(() => {
+ setBodyLength(message.body.length);
+ }, [message.body]);
<TextInput
model={message}
name="subject"
title="Ärende"
- onChange={() => setBodyLength(message.body.length)}
+ onChange={handleBodyChange}
/>
<Textarea
model={message}
name="body"
title="Meddelande"
- onChange={() => setBodyLength(message.body.length)}
+ onChange={handleBodyChange}
/>Also applies to: 117-117
| <a | ||
| className="uk-button uk-button-danger uk-float-left" | ||
| onClick={onDelete} | ||
| > | ||
| <i className="uk-icon-save" />{" "} | ||
| {member.id ? "Spara" : "Skapa"} | ||
| </button> | ||
| </div> | ||
| </form> | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
| <i className="uk-icon-trash" /> Ta bort medlem | ||
| </a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve delete button accessibility and semantics
The delete action uses an anchor tag (<a>) with onClick, which:
- Isn't semantic HTML (anchors are for navigation)
- May cause accessibility issues
- Doesn't prevent default action
- <a
- className="uk-button uk-button-danger uk-float-left"
- onClick={onDelete}
+ <button
+ type="button"
+ className="uk-button uk-button-danger uk-float-left"
+ onClick={(e) => {
+ e.preventDefault();
+ onDelete();
+ }}
- </a>
+ </button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <a | |
| className="uk-button uk-button-danger uk-float-left" | |
| onClick={onDelete} | |
| > | |
| <i className="uk-icon-save" />{" "} | |
| {member.id ? "Spara" : "Skapa"} | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| ); | |
| } | |
| } | |
| <i className="uk-icon-trash" /> Ta bort medlem | |
| </a> | |
| <button | |
| type="button" | |
| className="uk-button uk-button-danger uk-float-left" | |
| onClick={(e) => { | |
| e.preventDefault(); | |
| onDelete(); | |
| }} | |
| > | |
| <i className="uk-icon-trash" /> Ta bort medlem | |
| </button> |
| useEffect(() => { | ||
| const unsubscribe = collection.subscribe(({ page, items }) => { | ||
| setPage(page); | ||
| setItems(items); | ||
| setLoading(false); | ||
| }); | ||
| return () => { | ||
| unsubscribe(); | ||
| }; | ||
| }, [collection]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add cleanup and optimize the collection subscription effect
The current implementation has a few potential improvements:
- Missing cleanup of state when collection changes
- Hook dependencies could be optimized
Consider this implementation:
useEffect(() => {
+ // Reset state when collection changes
+ setPage({});
+ setItems(null);
+ setLoading(true);
+
const unsubscribe = collection.subscribe(({ page, items }) => {
setPage(page);
setItems(items);
setLoading(false);
});
return () => {
unsubscribe();
};
}, [collection]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| const unsubscribe = collection.subscribe(({ page, items }) => { | |
| setPage(page); | |
| setItems(items); | |
| setLoading(false); | |
| }); | |
| return () => { | |
| unsubscribe(); | |
| }; | |
| }, [collection]); | |
| useEffect(() => { | |
| // Reset state when collection changes | |
| setPage({}); | |
| setItems(null); | |
| setLoading(true); | |
| const unsubscribe = collection.subscribe(({ page, items }) => { | |
| setPage(page); | |
| setItems(items); | |
| setLoading(false); | |
| }); | |
| return () => { | |
| unsubscribe(); | |
| }; | |
| }, [collection]); |
| rows = items.map((item, i) => ( | ||
| <React.Fragment key={i}> | ||
| {rowComponent({ | ||
| item, | ||
| deleteItem: () => deleteItem(collection, item), | ||
| })} | ||
| </React.Fragment> | ||
| )); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid using array indices as React keys
Using array indices as keys can lead to performance issues and bugs with item reordering or deletion.
Consider using a unique identifier from the item:
-rows = items.map((item, i) => (
- <React.Fragment key={i}>
+rows = items.map((item) => (
+ <React.Fragment key={item.id || item.uniqueIdentifier}>Committable suggestion skipped: line range outside the PR's diff.
| import React, { useState } from "react"; | ||
|
|
||
| const FileInput = () => { | ||
| const [filename, setFilename] = useState(""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add missing state for upload handling
The component needs additional state variables to manage the upload process and provide feedback to users.
const [filename, setFilename] = useState("");
+const [uploadProgress, setUploadProgress] = useState(0);
+const [uploadStatus, setUploadStatus] = useState('idle'); // idle, uploading, success, error
+const [errorMessage, setErrorMessage] = useState('');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const [filename, setFilename] = useState(""); | |
| const [filename, setFilename] = useState(""); | |
| const [uploadProgress, setUploadProgress] = useState(0); | |
| const [uploadStatus, setUploadStatus] = useState('idle'); // idle, uploading, success, error | |
| const [errorMessage, setErrorMessage] = useState(''); |
| <input | ||
| id="upload-select" | ||
| className="uk-hidden" | ||
| type="file" | ||
| /> | ||
| </a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Connect file input and add upload feedback UI
The file input needs to be connected to handlers and the UI should show upload progress and errors.
<input
id="upload-select"
className="uk-hidden"
type="file"
+ onChange={handleFileSelect}
+ accept={acceptedFileTypes}
/>
+{uploadStatus === 'uploading' && (
+ <div className="uk-margin-small-top">
+ <progress className="uk-progress" value={uploadProgress} max="100" />
+ </div>
+)}
+{uploadStatus === 'error' && (
+ <div className="uk-alert uk-alert-danger">{errorMessage}</div>
+)}Committable suggestion skipped: line range outside the PR's diff.
| import React, { useState } from "react"; | ||
|
|
||
| const FileInput = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add missing props for component configuration
The component should accept props for configuring the upload behavior and handling file selection callbacks. Consider adding props like onFileSelect, acceptedFileTypes, etc.
-const FileInput = () => {
+const FileInput = ({ onFileSelect, acceptedFileTypes, maxFileSize }) => {Committable suggestion skipped: line range outside the PR's diff.
| const handleDayChange = (date, type) => { | ||
| if (type === "start") { | ||
| period.start = date || null; | ||
| setStart(date || null); | ||
| } else if (type === "end") { | ||
| period.end = date || null; | ||
| setEnd(date || null); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding date validation and avoiding direct mutations.
-
The direct mutation of the period object (
period.start = date) could lead to unexpected behavior. Consider using a proper update method if provided by the period object. -
There's no validation to ensure the end date isn't before the start date.
const handleDayChange = (date, type) => {
+ // Validate date range
+ if (type === "end" && date && start && date < start) {
+ return; // or show error message
+ }
+ if (type === "start" && date && end && date > end) {
+ return; // or show error message
+ }
+
if (type === "start") {
- period.start = date || null;
+ // Consider using period.updateStart(date) if available
+ period.start = date || null;
setStart(date || null);
} else if (type === "end") {
+ // Consider using period.updateEnd(date) if available
period.end = date || null;
setEnd(date || null);
}
};Committable suggestion skipped: line range outside the PR's diff.
| useEffect(() => { | ||
| if (props.dataSource) { | ||
| let options = []; | ||
| let newOptions = []; | ||
| if (props.nullOption) { | ||
| options = [props.nullOption]; | ||
| newOptions = [props.nullOption]; | ||
| } | ||
| get({ url: props.dataSource, params: { page_size: 0 } }).then( | ||
| (data) => this.setState({ options: options.concat(data.data) }), | ||
| (data) => setOptions(newOptions.concat(data.data)), | ||
| () => null, | ||
| ); | ||
| } | ||
| } | ||
| }, [props.dataSource, props.nullOption]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Network request in SelectInput lacks cleanup, unlike other components
The search results show that while most components in the codebase properly implement cleanup for subscriptions, the SelectInput component's useEffect with the network request doesn't handle cleanup for potential in-flight requests. This could lead to memory leaks or race conditions if the component unmounts while the request is pending.
admin/src/Components/SelectInput.jsx: Add cleanup to the network request useEffect:
useEffect(() => {
if (props.dataSource) {
let aborted = false;
let newOptions = [];
if (props.nullOption) {
newOptions = [props.nullOption];
}
get({ url: props.dataSource, params: { page_size: 0 } }).then(
(data) => !aborted && setOptions(newOptions.concat(data.data)),
() => null,
);
return () => { aborted = true; };
}
}, [props.dataSource, props.nullOption]);🔗 Analysis chain
Verify cleanup of subscriptions and network requests
Given the reported issues with infinite network requests in other components, let's verify the cleanup implementation across similar components.
Also applies to: 24-37
Let me try a different search pattern to find potential cleanup issues in useEffect hooks.
The previous pattern might have been too specific. This new search will help us:
- Find all useEffect hooks that make network requests
- Find any cleanup functions implemented in useEffect hooks
This will give us a better picture of the cleanup implementation across components.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for components with potential cleanup issues
echo "Searching for components with useEffect and network requests..."
ast-grep --pattern 'useEffect(() => {
$$$
get({
$$$
})
$$$
})' admin/src/Components/
echo "Searching for components with useEffect and subscriptions..."
ast-grep --pattern 'useEffect(() => {
$$$
subscribe($$$)
$$$
})' admin/src/Components/
Length of output: 461
Script:
#!/bin/bash
# Search for all useEffect hooks in Components directory
echo "Searching for useEffect hooks with network requests..."
rg -U "useEffect\(.+\{[\s\S]+?get\(.+?\)[\s\S]+?\}.*\)" admin/src/Components/ -A 5
echo "Searching for cleanup functions in useEffect..."
rg -U "useEffect\(.+\{[\s\S]+?return\s+\(*\s*\(*\)\s*=>\s*\{[\s\S]+?\}" admin/src/Components/
Length of output: 12610
| useEffect(() => { | ||
| const handleProductChange = () => { | ||
| const newActions = product.actions; | ||
| const newAvailableActionTypes = filterAvailableActions(newActions); | ||
| const selectedActionType = filterSelectedActionType( | ||
| selectedActionType, | ||
| newAvailableActionTypes, | ||
| ); | ||
|
|
||
| setActions(newActions); | ||
| setAvailableActionTypes(newAvailableActionTypes); | ||
| setSelectedActionType(selectedActionType); | ||
| setSaveDisabled(!product.canSave()); | ||
| }; | ||
| } | ||
|
|
||
| componentDidMount() { | ||
| const { product } = this.props; | ||
| this.unsubscribe = product.subscribe(() => | ||
| this.setState(productChanged), | ||
| ); | ||
| } | ||
| const unsubscribe = product.subscribe(handleProductChange); | ||
| handleProductChange(); | ||
|
|
||
| componentWillUnmount() { | ||
| this.unsubscribe(); | ||
| } | ||
| return () => { | ||
| unsubscribe(); | ||
| }; | ||
| }, [product, selectedActionType]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix potential infinite loop in useEffect
The useEffect hook includes selectedActionType in its dependency array while also updating it inside the effect. This can cause an infinite loop of updates.
Apply this fix to remove the unnecessary dependency:
useEffect(() => {
const handleProductChange = () => {
const newActions = product.actions;
const newAvailableActionTypes = filterAvailableActions(newActions);
const newSelectedActionType = filterSelectedActionType(
selectedActionType,
newAvailableActionTypes,
);
setActions(newActions);
setAvailableActionTypes(newAvailableActionTypes);
setSelectedActionType(newSelectedActionType);
setSaveDisabled(!product.canSave());
};
const unsubscribe = product.subscribe(handleProductChange);
handleProductChange();
return () => {
unsubscribe();
};
- }, [product, selectedActionType]);
+ }, [product]); // selectedActionType is not needed in dependencies📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| const handleProductChange = () => { | |
| const newActions = product.actions; | |
| const newAvailableActionTypes = filterAvailableActions(newActions); | |
| const selectedActionType = filterSelectedActionType( | |
| selectedActionType, | |
| newAvailableActionTypes, | |
| ); | |
| setActions(newActions); | |
| setAvailableActionTypes(newAvailableActionTypes); | |
| setSelectedActionType(selectedActionType); | |
| setSaveDisabled(!product.canSave()); | |
| }; | |
| } | |
| componentDidMount() { | |
| const { product } = this.props; | |
| this.unsubscribe = product.subscribe(() => | |
| this.setState(productChanged), | |
| ); | |
| } | |
| const unsubscribe = product.subscribe(handleProductChange); | |
| handleProductChange(); | |
| componentWillUnmount() { | |
| this.unsubscribe(); | |
| } | |
| return () => { | |
| unsubscribe(); | |
| }; | |
| }, [product, selectedActionType]); | |
| useEffect(() => { | |
| const handleProductChange = () => { | |
| const newActions = product.actions; | |
| const newAvailableActionTypes = filterAvailableActions(newActions); | |
| const selectedActionType = filterSelectedActionType( | |
| selectedActionType, | |
| newAvailableActionTypes, | |
| ); | |
| setActions(newActions); | |
| setAvailableActionTypes(newAvailableActionTypes); | |
| setSelectedActionType(selectedActionType); | |
| setSaveDisabled(!product.canSave()); | |
| }; | |
| const unsubscribe = product.subscribe(handleProductChange); | |
| handleProductChange(); | |
| return () => { | |
| unsubscribe(); | |
| }; | |
| }, [product]); // selectedActionType is not needed in dependencies |
🧰 Tools
🪛 Biome (1.9.4)
[error] 35-35: This variable is used before its declaration.
The variable is declared here:
(lint/correctness/noInvalidUseBeforeDeclaration)
emanuelen5
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few things we should clean up, but we can do all of that in follow-up PRs. This seems to work, and is quite the improvement. Thanks a lot! ❤️
| ); | ||
| }; | ||
|
|
||
| export default withRouter(KeyHandout); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove withRouter
| children: PropTypes.node, | ||
| }; | ||
|
|
||
| export default withRouter(MemberBox); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove withRouter
| MemberBox.propTypes = { | ||
| children: PropTypes.node, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove propTypes
| e.preventDefault(); | ||
| createKey(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, instead remove e.preventDefault() and keep return false.
| const collectionRef = useRef( | ||
| new Collection({ | ||
| type: Group, | ||
| url: `/membership/member/${props.match.params.member_id}/groups`, | ||
| idListName: "groups", | ||
| pageSize: 0, | ||
| }); | ||
| this.state = { | ||
| items: [], | ||
| options: [], | ||
| showOptions: [], | ||
| selectedOption: null, | ||
| }; | ||
| }), | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useRef won't work here if you re-render with an updated member_id - then the collection will still use the old member's data. Instead, I think the easiest is to use useMemo with the member_id as dependency.
| const collectionRef = useRef( | ||
| new Collection({ | ||
| type: Span, | ||
| url: `/membership/member/${props.match.params.member_id}/spans`, | ||
| url: `/membership/member/${memberId}/spans`, | ||
| pageSize: 0, | ||
| includeDeleted: true, | ||
| }); | ||
| this.state = { items: [], pending_labaccess_days: "?" }; | ||
| this.pending_actions = get({ | ||
| url: `/membership/member/${props.match.params.member_id}/pending_actions`, | ||
| }).then((r) => { | ||
| const sum_pending_labaccess_days = r.data.reduce((acc, value) => { | ||
| if (value.action.action === ADD_LABACCESS_DAYS) | ||
| return acc + value.action.value; | ||
| return acc; | ||
| }, 0); | ||
| this.setState({ | ||
| pending_labaccess_days: sum_pending_labaccess_days, | ||
| }); | ||
| }); | ||
| } | ||
| }), | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here:
useRef causes the collection to never update, even if the member_id is changed. That is a bug. To fix that, you can either create the collection on each render:
const collection = new Collection(...);or memoize it with correct dependencies, to just create it when needed:
const collection = useMemo(() => new Collection(...), [member_id]);| useEffect(() => { | ||
| const unsubscribes = []; | ||
|
|
||
| // Subscribe to spans updates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove comment
| componentWillUnmount() { | ||
| this.unsubscribe.forEach((u) => u()); | ||
| } | ||
| // Subscribe to categoryPeriods updates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove comment
| // post({ | ||
| // url: `/webshop/member/${member_id}/ship_labaccess_orders`, | ||
| // expectedDataStatus: "ok", | ||
| // }); | ||
| post({ | ||
| url: `/webshop/member/${member_id}/ship_labaccess_orders`, | ||
| headers: { "Content-Type": "application/json" }, | ||
| payload: { | ||
| /* any required data */ | ||
| }, | ||
| expectedDataStatus: "ok", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert. Or was there a reason it had to change?
| function MemberBoxSpans(props) { | ||
| const memberId = props.match.params.member_id; | ||
|
|
||
| const [, setItems] = useState([]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the unused state










What:
admin/src/Models/CollectionNavigation.js, contains aCollectionNavigationclass that serves as a base class for multiple other components. To maintain consistency and avoid introducing incomplete changes, we opted to leave this class unchanged for now. After investigating related dependencies, a new issue has been created to address this component separately (Refactor CollectionNavigation and Related Components to Function Components #582 ).Why:
Modernizing the codebase to align with current React best practices. Function components are more concise, easier to read, and enable the use of modern React features like hooks.
Testing:
make testSummary by CodeRabbit
Release Notes
New Features
Bug Fixes
Refactor
These changes improve the overall user experience by making the application more responsive and easier to maintain.