Skip to content
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

Redux cleaning #550

Merged
merged 29 commits into from
May 3, 2022
Merged

Redux cleaning #550

merged 29 commits into from
May 3, 2022

Conversation

yesean
Copy link
Contributor

@yesean yesean commented Mar 11, 2022

This PR is a refactor of our redux usage, most notably the help of redux-toolkit. The main goal was to simplify the code and reduce the amount of boilerplate brought on by redux. This is achieved in multiple ways:

  1. Using toolkit's createSlice and createAsyncThunk to define redux reducers and actions. These helper functions reduce a lot of redux code (with the help of immutable-js and typescript) which allows all of the redux to be colocated into a single *Slice.ts file.
  2. Replacing the use of the connect HOC with the useDispatch and useSelector hooks which effectively remove the need for most container components.
  3. General purpose refactoring to keep the code (including some redux-adjacent stuff) as DRY but simple as possible.

Addtional notes:

  • *{Reducer,Actions,Types}.ts files have been replaced with *Slice.ts and a utils.ts. the slice file holds all of the redux logic and the utils file holds the non-redux logic (fetching, notifying, etc). generally, the functions in utils are imported into the slice to provide any needed redux for the utility. this was done to decouple the redux and non-redux logic as much as possible.
  • the redux_reducers.ts, combineReducers, createStore setup has been replaced with the simpler redux/store.ts, configureStore (the toolkit recommended way)
  • redux-toolkit provides excellent typescript support for the store and actions which was previously severely lacking. I tried to type as much as I could but some parts of the store (in particular, the event slice) are poorly typed (dynamically changing slice structures) so it was hard to infer exactly what fields would be in each slice. In any case, the current type safety is far better than before (some fields may still be missing though).
  • I needed the Awaited utility type which was introduced in Typescript 4.5 so I upgraded the typescript package to latest. Unfortunately, typescript changed their AST in 4.0 but create-react-app uses an older, incompatible version of typescript-eslint/parser so I used selective dependency resolutions (resolutions in package.json) to force the new version of the eslint parser. Nevertheless, everything is working fine now, though this change may be better suited for another PR?
  • I was pretty aggresive with the refactor and I did change some of the business logic and I'm not too familiar all of the inner workings of the portal so I may have made some wrong assumptions about various processes (auth, checkin, etc). I haven't tested it too extensively, for the most part, it works but I'm hoping this PR can not only serve as a code review but an open call for testing.

@yesean yesean added the PR: Needs Review This PR needs review label Mar 11, 2022
@yesean yesean self-assigned this Mar 11, 2022
@StormFireFox1
Copy link
Member

This is some great work, @yesean, well done!

I've looked over your PR at a first glance, and overall, it's good! I haven't reviewed the business logic you've changed yet, but I'll probably do that after finals week. I think there's a couple questions that came up while I looked over it we might want to discuss:

  • Currently, most of each feature's utils.ts is effectively the API calls, whereas conventionally utilities were simply local functions we used to simplify business logic. Currently, @steets250 was moving API calls under an API folder of sorts, maybe we wanna do something similar here.
  • Also, on a more fun note, like you said, I don't think we need containers anymore! It's kinda hard to truly move away from them, considering how prominent they are all around the code, so I suggest we rename all our containers to pages, which makes more sense as to what purpose they serve; representing each page from our portal.

What do y'all think of this?

@steets250
Copy link
Member

Ah sorry I've been MIA for the past week(s) about this, you can completely ignore the api-updates branch, that was my wild adventure of "oh, now that this is fixed I can also fix this", which I'm still in the process of splitting up into more reasonably sized PRs. Tomorrow I'll take review this, and hopefully the switch to separate api functions can just happen in place once these changes are either merged in, or a PR can be opened against this branch instead if we want to hold off on merging until everything is done.

const EditEventCustomForm = (EditEventForm as unknown) as React.ComponentClass<any>;
const EditEventCustomForm = EditEventForm as unknown as React.ComponentClass<any>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for now, I opened #551 which I think will address some of the weird Typescript casting we have throughout the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That must've come from prettier, I don't recall manually editing that. as is probably left associative so the parenthesis don't matter.

@@ -64,4 +63,4 @@ const FormikCreateEventForm = withFormik({
},
})(CreateEventForm as React.FC);

export default connect(null, { postEvent, copyLink })(FormikCreateEventForm);
export default connect(null, { postEvent })(FormikCreateEventForm);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you addressing all instances of connect( in this PR? You mention this in your second point, but I'm still seeing instances of it in use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only instances where I left connect was with Formik since there isn't a way to access the useDispatch or useSelector hooks with the withFormik HOC. In order to remove the connect usage there, we'd have to rewrite the Formik code with Formik hooks or different component APIs which is probably better suited for another PR.

import './style.less';
import { verifyEmail } from '../../utils';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep import './style.less'; as the last import?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For every file that I modified, I ran Typescript's built in organizeImports command from the language server tsserver. I'm not sure if we can configure it, but I like having an automated, opinionated formatter for imports.

import './style.less';
import { sendEmailVerification } from '../../utils';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

@@ -1,50 +1,32 @@
import React, { useEffect } from 'react';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this, requireAuth, and requireStoreAccess will need to be re-applied to Paul's new access protection component. We'll want to merge from master to incorporate those changes.

Comment on lines 26 to 39
<NavTileItem icon={DashboardIcon} text="Dashboard" />
<NavTileItem icon={DashboardIcon} />
</NavLink>
<NavLink activeClassName="selected" to="/leaderboard">
<NavTileItem icon={LBIcon} text="Leaderboard" />
<NavTileItem icon={LBIcon} />
</NavLink>
<NavLink exact activeClassName="selected" to="/profile">
<NavTileItem icon={ProfileIcon} text="Profile" />
<NavTileItem icon={ProfileIcon} />
</NavLink>
<NavLink activeClassName="selected" to="/discord">
<NavTileItem icon={DiscordIcon} text="Discord" />
<NavTileItem icon={DiscordIcon} />
</NavLink>
{isAdmin && (
<NavLink activeClassName="selected" to="/admin">
<NavTileItem icon={AdminIcon} text="Admin" />
<NavTileItem icon={AdminIcon} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do these changes tie in to redux stuff?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The text prop was unused in the NavTileItem component so I removed it. I think it came up when I was resolving lint warnings.

const cart = JSON.parse(serializedCart);
return cart as Cart;
} catch (err) {
return {};
Copy link
Member

@steets250 steets250 Mar 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we console.log for store, shouldn't we do the same for load? And perhaps it should be a notify so that the user sees that where was an error and it's not a mystery why something didn't save/load.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I was planning on removing the log and doing what loadCart does. But I like the idea of adding a notify so something like:

try {
// ...
} catch (err) {
	notify(err);
	return {};
}
// ...

Does that sound good?

Copy link
Member

@StormFireFox1 StormFireFox1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some nits for any things Steven didn't mention that I thought would be important but otherwise, before we merge, I feel like we should still discuss the items I mentioned in my first comment before we merge.

@@ -0,0 +1,36 @@
/* eslint-disable no-param-reassign */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The no-param-reassign rule in our eslint config has the option {props: true} (from airbnb) which forbids mutating properties of parameters to reduce side effects. However, since redux-toolkit uses immer for createSlice, it's more idiomatic to mutate the state argument when defining reducers since it'll be equivalent to copying the parameter and is generally easier to read as well. I still think it's a good rule, so I've just disabled it in all slice files since createSlice will usually mutate the state parameter.

redux-toolkit has a discussion on this which tldr: airbnb is too restrictive, just use {props: false} which is the eslint default anyway.

Thoughts?

import React, { useState, ChangeEventHandler, FocusEventHandler, FormEventHandler } from 'react';
import { AutoComplete, Checkbox, Form, Input, Button, Tooltip, Tag, Icon, Select } from 'antd';
import { AutoComplete, Button, Checkbox, Form, Icon, Input, Select, Tag, Tooltip } from 'antd';
import React, { FormEventHandler, useState } from 'react';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically, I think in React the 'react' import should always be first in file (unless you're in Next.js, where you don't need it). I might wrong on this, and CRA might have removed that need. We should readjust that in files where it happens.

Copy link
Contributor Author

@yesean yesean Mar 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above. I don't believe the react import needs to come first, but it may be more idiomatic. Also, I think you're referring to the new JSX Transform where the React import is not needed and JSX completely handled at compile time. Unless we need to use React 16.3, we could upgrade to 16.4 or 17 to take advantage of it. Facebook even provides a codemod to automatically remove the unneeded imports. All we'd have to do is upgrade react and react-scripts I believe.

@yesean
Copy link
Contributor Author

yesean commented Mar 29, 2022

This is some great work, @yesean, well done!

I've looked over your PR at a first glance, and overall, it's good! I haven't reviewed the business logic you've changed yet, but I'll probably do that after finals week. I think there's a couple questions that came up while I looked over it we might want to discuss:

  • Currently, most of each feature's utils.ts is effectively the API calls, whereas conventionally utilities were simply local functions we used to simplify business logic. Currently, @steets250 was moving API calls under an API folder of sorts, maybe we wanna do something similar here.

I don't really see the distinction between API calls and business logic as API calls feel like another step in the logic. Moreover, the calls are generally localized to a section of the store and not shared between sections, so to me, it makes sense to keep them at a section-level rather than the top-level. Perhaps, utils isn't best place for API calls but I still think they should be localized rather than a top level API folder. Of course, if a backend API package is introduced, this may all be irrelevant.

  • Also, on a more fun note, like you said, I don't think we need containers anymore! It's kinda hard to truly move away from them, considering how prominent they are all around the code, so I suggest we rename all our containers to pages, which makes more sense as to what purpose they serve; representing each page from our portal.

pages sound good to me, glad to be moving away from containers 😀

What do y'all think of this?

@StormFireFox1
Copy link
Member

Just to recap all the discussion from above and meetings:

  • Add ESLint plugin for organizing imports
  • Handle merge conflicts for Paul's PR
  • Disable ESLint rule for no-param-reassign

@yesean
Copy link
Contributor Author

yesean commented Apr 26, 2022

Just to recap all the discussion from above and meetings:

  • Add ESLint plugin for organizing imports
  • Handle merge conflicts for Paul's PR
  • Disable ESLint rule for no-param-reassign

Fixed

Copy link
Member

@StormFireFox1 StormFireFox1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally looks good to me! 🚀 👀

@yesean yesean merged commit 2d4bdb8 into master May 3, 2022
@yesean yesean deleted the redux-cleaning branch May 3, 2022 03:34
trevorkw7 added a commit that referenced this pull request Oct 19, 2022
Redux Cleaning (#550) fails to run at production while containing too many file changes to individually bug fix and address. Thus, a decision was made to revert this commit and all commits after this commit was made.
trevorkw7 added a commit that referenced this pull request Oct 19, 2022
Redux Cleaning (#550) fails to run at production while containing too many file changes to individually bug fix and address. Thus, a decision was made to revert this commit and all commits after this commit was made.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR: Needs Review This PR needs review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants