-
Notifications
You must be signed in to change notification settings - Fork 16
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
Break up app.js
#168
Comments
I'm looking for ideas on how to move this work forward. So far, my current best plan is an ambitious re-write of the whole app:
This approach would halt all other progress on the app, take a long time and commitment, and have lots of subtle changes in behaviour to the app. I would love a better plan than this! |
I also felt that State management libraryProviding states as React Context you mentioned is one immediate solution to prop drilling, but I don't recommend it as a long-term solution because performance is not out of box and there are some caveats on implicit dependencies between context (e.g. if inner context value was read by outer context, outer context gets a default value of inner context without errors). State management libraries come into this area and do a really good job. Redux has been a pioneer in this area, but I find Jotai useful with its simple and powerful concepts of atoms for defining states and derived data separated from component trees. (Recoil explains it very well with the videos their or the motivation page. It's very similar library to jotai from Meta but dev team has been laid off) I think Typey Type is a data-intensive app and it's a good match with the easy defining of derived data. With the library, states and logic around them could be placed in separate directories and independent from component tree, and consumer of states or trigger of state updates would directly consume using hooks like Modified excerpts from na2hiro@227dbe9#diff-512813af7a666922f452efd7095a944b07bf9d44976a2431a04737e934f9ec2c: // (metWordsState.ts)
export const metWordsState = atom({} as MetWords);
// derived data. auto-updated when metWordsState changes
export const yourSeenWordCountState = atom((get) => calculateSeenWordCount(get(metWordsState))); Example of consuming atoms // (Consumer.tsx)
const [metWords, setMetWords] = useAtom(metWordsState)
const [yourSeenWordCount, _] = useAtom(yourSeenWordCountState) Integrating state management library doesn't mean we need to switch everything at once. We can migrate state by state while keeping everything working. Since states can use another states for its updating logic etc, it's straightforward if underlying data is already managed by the same library. So the first candidate to migrate would be a state that doesn't depend on any other states. ( I made some PoC commits on this branch https://github.com/na2hiro/typey-type/commits/jotai and left comments for the important part of the changes. I think last two commits around Observation on
|
Thanks so much for sharing your thoughts here @na2hiro! I appreciate the time you've given this and the effort to include PoC commits. A lot of what you've shared makes sense, and I'll follow up with more concrete thoughts on next steps when I have the head space. |
Ok, we can migrate to using a state management library. I'd be open to jotai or zustand. Let's start using jotai. Yes, the setState callbacks in app.js are the places I expect the most hassle. Some user-facing behaviour changes may be necessary, which I hope to at least make conscious decisions around (rather than accidental changes). From memory, user settings is read in a few places outside of lessons. Let's start reducing app.js size and migrating state, then convert to TS file with ignored errors. I'll be away for a week or so without a computer but when I'm back I'd like to review the code to set/load personal preferences, storage code, and file loading. In the meantime, any incremental PRs for this issue are welcome. |
Thanks for checking. Yeah, I'll try to clarify what's going to be changed as much as possible. To move forward with extracting user settings, I resumed from the second commit of https://github.com/na2hiro/typey-type/commits/jotai. However, as I go further, I feel more that I cannot make so granular commit right now.
Please take your time and there's no need to rush. I had 10 days vacation last week and that was plenty of time for fun 😅 I'm going to slow down as well |
Just an overall progress update on this issue. Here are all the PRs progressing this work so far:
The main state remaining in |
Probably you'd want to move the state management down to the components that are using and updating that state. For example - the lesson state and lesson handling functions could be moved to the Lesson component. That would reduce the app.js and the amount of prop drilling. |
@didoesdigital what if there were a custom hook The hook would return the lesson methods and the lesson state - I'd try to implement that if it sounds reasonable. If this works, It should be fairly simple - the App.js would be copied to useLesson.js, have the irrelevant code removed and refactor the file a hook using the state defined by jotai atoms, then instead of props in the components or context use the the same values from the hook. Finally, clean up the App.js |
Sounds great @evgenyfadeev ! We might need to keep a few pieces out of the lesson code, like the personal/global dictionaries and met words. I would guess converting the Feel free to have a go! Draft/progress PRs are welcome too. |
@didoesdigital is it ok to use absolute imports? That is instead of
It does seem that some parts could go to some other state, e.g. metricsState. It could also be split out later though.
Right, I see. Perhaps some of those callback chains are not really necessary, let's see how it goes. |
@evgenyfadeev using absolute imports and introducing them in new files sounds ideal. I vaguely recall running into issues trying to use them in Typey Type in the past, though I cannot remember the details (it might have been an issue with an older version of Create React App or TypeScript, or an interaction with something like Storybook/Jest/Sentry). It's probably fine now. You might need to add a |
Regarding the absolute imports: the app and the tests are working with absolute imports. Speaking of the tests - does "yarn test" run all the tests, or yarn test:ui and test:unit do something in addition? What would be a way to see if sentry is working? Most likely I'll make some prep PR's first, flattening some setState callback chains. |
Yarn test should run all the tests. Because test:ui is slow you might want to just run the faster test:unit while iterating on logic then Does that all make sense? |
Regarding Sentry, I'm not sure of a good option to check the absolute imports are fine in sourcemaps, maybe just put it on a small PR and I'll deploy it and then poke around in the Sentry UI. I don't expect it to be a problem. |
PR #198 removes part of the If this is ok, I can remove the remaining setState chains using the same way. @didoesdigital The idea is that code like Would be replaced with Btw, something to look at: the setupLesson method implemented here will apply the patch newState in the end of the function. This probably makes sense, but it is not how it was done before. (The original logic has the new State applied first and then setupLesson() wourd run). I have a branch app-js-remove-imperative-focus-flatten-setstate-2, where the order of the patch application is more closely matching the original code, but there I have a bug that the revision lesson would not load any content. |
The
app.js
file is huge, disorganised, unformatted, has 1 giant appstate
, is a class component, and lacks types. I want to makeapp.js
and changing Typey Type more manageable.Background
Typey Type started out as a single component, single page (no routes) app about 7 years ago before React hooks existed. I've been progressively breaking down components into smaller, more manageable parts, with more folder structure, and updating them to TypeScript with a preference for function components so I can use hooks (see examples: #144, #145, #146, #147, #148, #149, #150). But
app.js
is so large, tangled, and unwieldy that it is difficult to break up.Goals
App
and everything inapp.js
was using function components, it would be easier to use hooks in more places, which are extremely helpful.app.js
were smaller, it would be easier to add types and convert components to TypeScript to make changes easier and more maintainable.Approach
While I prefer structured refactoring with small, isolated, always-working commits, I may be reaching the limits of what's left in
app.js
that can be broken down a bit at a time.The text was updated successfully, but these errors were encountered: