Roren
Roren
Explore posts from servers
TTCTheo's Typesafe Cult
Created by Roren on 12/12/2023 in #questions
Type error when assigning across objects with matching shape
Hey there, I'm working on migrating a codebase to TS right now, and I was curious about some behavior I saw from the TS compiler. I'm sorry if something like this has been asked - I tried looking, but wasn't sure what terminology to best use. It looks like if I have two objects with matching types but DIFFERING value types depending on key, because the value of obj2[key] ends up being a union of all possible types, I can't simply assign to obj1[key] with that value. I understand what's going on, though my understanding of when this would come into play is pretty loose. It looks like if I add a condition that does exactly the same thing, but splits off the various types into different blocks of the condition, it works. TS Playground example here: https://www.typescriptlang.org/play?#code/C4TwDgpgBA8gRgKwMoAsCGkoF4oG8BQUUaAXFAM7ABOAlgHYDmA3IVHGQUUQMZmW2MWRAL75hLfNwD2dSlCmIAjGXjJ0mHJ2JkA5CggAbA1J0AaVuzyseuhlKkATOCAg7Wo8fkky5ChACYVRFQMaE1WUigdAHcIYDMLDmsoXiiHKhA3ETEJaVlgKABrCBBybFhECG5gADpi0oAKP38ASlyfAppyVUUAaRLyhv4+anoGFpGqKC6ikqkAMyhQSAX5JWwAPisiKjiAVyo6CoQq2rAqKWBL8Aga9G7ougAFC8gqUBruNCMmpVMKahtHJeeZSKYNAxxablAAMTGhAB5ZqUapDGMAUPCaABqbEtbYpDrI8r1cgAbRoAF0hNNFg0AIRdHr9EANeotfFaHgyYD0PYQGmiIh+RRk+qU8rNMUlaliEFgqAQqE0WFYqBI0moiDozHTXGc1h5OT1EklclUmk0OmM7pKFlskocgncui8uj8wWsK2Kk1YP1ROA6A1cNYIUXiyWIfzSkCykRQQzkaBc0PhmWRgIxuNQDz4IA I suppose in the given example, this works because the compiler then knows exactly what type obj1[key] and obj2[key] are, and can reason about them and confirm it's safe. At the end of the day, I'm just doing the same operation though - is this an example of the compiler not being "smart enough" to understand that they keys between objects are counterparts of one another? Sort of like how I can't narrow a type with .includes because the compiler doesn't "get it"? I'm SURE there's a better way to represent this to the compiler - does anyone have advice on how to do that? Thank you so much for taking the time to read this.
1 replies
TTCTheo's Typesafe Cult
Created by Roren on 6/22/2023 in #questions
Use different layouts at high level of tree without clobbering all local state
Hey there! I hope you're having a great day. I'm working on a WYSIWYG editor for my employer, and I'm running into a bit of an issue and I wasn't sure the best way to solve this. There are multiple different "layouts" that can be selected, which correspond to a user's desired layout of the page (e.g. page as viewed on mobile vs. desktop, show these items in accordion rather than tab, etc.). At a high level of the component tree, we've got items like <StandardLayout /> and <MobileLayout /> that we conditionally render depending on what the user has selected. This will retain the widgets a user has added to the page, but simply format things in a different way. However, this also effectively clobbers all local state going all the way down. Often times this is a minor inconvenience - we can simply elevate the state for one or two necessary values. However, recently I've been working on giving two widgets the ability to execute an image upload process at the same time as one another, and have provided each with its own context provider to faciltiate this, and I'm noticing this can be a serious usability issue. I could, of course, simply disable the ability to change view until I know the "coast is clear", so to speak, but I wasn't sure if there was a better way, as I'm sure this is a common enough problem. I figure I could elevate the state more globally, and simply keep state for each widget in an object they can index into with their ID, but each one simply having a more local context seems more clean to me. I was hoping perhaps there was a way to leverage portals for this - wrap the context provider around the entire app rather than locally around the widget when it renders, but I haven't been able to figure that out and it doesn't really seem workable. Maybe something like leveraging local storage when I detect a change is happening? If anyone has any advice, I'd really appreciate it. I hope you're having a great day - thank you!
1 replies
TTCTheo's Typesafe Cult
Created by Roren on 4/17/2023 in #questions
CRA Alternatives for SPA
Happy Monday everyone! I just wanted to check from some people that are likely more informed than me on this. Right now my employer has most everything built with CRA. This has for the most part not really mattered for us, but in one particular app we're needing to use libraries to sort of "crack open" the build and modify it - libraries which don't seem to really be maintained at this point. I'm going to be starting a spike to examine alternatives for us, as we're making major changes and have been given the go-ahead to modernize a few things (e.g. JS to TS, getting rid of CRA). Unless I'm mistaken, I believe my employer would like to keep the app as a SPA, as they want it to exist in an S3 bucket without having a server (app doesn't have any need for SEO so it's been deemed unnecessary). My go-to choice would be Vite, but my employer is using webpack for bundling everywhere, and they tend to be conservative about diversifying tools. Are there any other good tools for SPA, without using CRA and using webpack for bundling? I could set the project up manually, but I wanted to check if there's anything similar to Vite, with convenient and sane defaults for the build, but which uses Webpack instead.
14 replies
TTCTheo's Typesafe Cult
Created by Roren on 3/21/2023 in #questions
Display modal on back button click
Hey all, I've received a task wherein we're displaying a modal on an order form when the user takes certain actions that will trigger a modal offering a discount or some other product depending on a vendor's settings. One of the requests is to trigger the modal when the user clicks the back button. Essentially, the user clicks back, the modal pops up saying "before you go, here's the offer", and then never triggers again. As annoying as it is, I figure I should look into how this could be done. My idea was to pushState under certain circumstances a dummy entry, so that when the user clicks back to leave the SPA, there's that buffer for us to detect popstate, show the modal, and then flip a boolean that will fix things up to allow back navigation afterward. However, it seems browsers have begun trying to skip duplicate entries when back is clicked, seeing discussions such as https://github.com/WICG/interventions/issues/21 and https://github.com/whatwg/html/issues/7832. I've thought about potentially doing some kind of client-side routing (we use React Router), and a <Prompt /> that'll get me there, but that seems like a bad idea both because it's hacky and because I don't believe navigation blocking are available in the same way in the newest version of RR. It's difficult for me to determine if there is left a way to implement this reliably, or if it's effectively (and probably rightfully) not possible. I figure even if I could, it's liable to break as browser versions are released and the discussions continue. Is anyone aware of a way I could do this reliably, and in such way that it wouldn't be any more of a headache to the user than it has to be?
1 replies
TTCTheo's Typesafe Cult
Created by Roren on 2/28/2023 in #questions
Reasons not to mutate React state
Hey everyone, this topic came up when I was chatting with a coworker who was less familiar with React, and I was hoping someone could shed some light on what is probably a very basic question. If I have some piece of state, say for example const [person, setPerson] = useState({ name: 'Ed', age: 99 });, obviously if I were to mutate the object directly, it would not trigger any update and we'd be left with stale state. However, if an update WERE to occur in lockstep with this mutation, would that have negative implications beyond simply not being a best practice? For example...
const [person, setPerson] = useState({ name: 'Ed', age: 99 });
const [_, forceUpdate] = useState();

const change = () => {
person.name = 'The Cooler Ed';
forceUpdate();
};
const [person, setPerson] = useState({ name: 'Ed', age: 99 });
const [_, forceUpdate] = useState();

const change = () => {
person.name = 'The Cooler Ed';
forceUpdate();
};
Obviously this is a contrived example, but you can see the point. The coworker in question is newer to React, and was mutating state but then another update was causing an update, so he hadn't realized it was an issue. I explained that this isn't good because it may introduce a bug if the code changes in the future, but I couldn't exactly describe whether or not there are any issues with changing prior state BEYOND this. Would it introduce issues? Or is it simply a matter of an update not occurring, which I've already described?
12 replies
TTCTheo's Typesafe Cult
Created by Roren on 2/8/2023 in #questions
SPA state in Zustand store being preserved across page navigation?
I have a form which gives users the ability to submit payments via either credit card or PayPal. When a user selects PayPal, the form is toggled into a loading state with overlay, and the user is redirected to a PayPal sandbox URL. To my understanding, this should mean the app unloads and all state would be reset fresh with another load. However, I'm observing that when using Incognito mode (not sure why it's only consistent that way), hitting the "back" button from that PayPal location will bring the user back to the form, with state still preserved. The overlay is active, all form data is saved, etc. Do browsers preserve such state for recent pages? Is there a way I can consistently ensure this doesn't happen? If a user hits "back" I'd ideally like to simply trigger a location.reload() for simplicity.
3 replies
TTCTheo's Typesafe Cult
Created by Roren on 1/12/2023 in #questions
Infinite Error when React error boundary uses unrenderable MUI components
Hey all, I hope you're doing well. I'm running into an issue that's been confusing me related to React error boundary. Essentially, we've got an Error Boundary nested inside a MUI theme provider which uses a number of style overrides for MUI components. When an app is unable to load these components (the specific situation we're running into is when Object.entries is unavailable due to a call in createStyled.js in @mui/system), the error boundary seems to kick in as expected. However, within that boundary, we are also using MUI components that are subject to this same problem. I would expect that if a component within the boundary were to error in such a manner, it would simply exit the boundary and result in an uncaught error, breaking the client. However, what we're observing is that an infinite error occurs, wherein these components continue to try to render over and over again. Is the boundary catching its own error? I have a sandbox reproduction here. Please keep in mind it will break the tab - the way I've been able to observe the error is by selecting Open in New Window, copying the link, and then pasting this link into another tab with the developer console already open. There are a few ways I could mitigate this (e.g. move the boundary outside of the ThemeProvider and style separately, polyfill Object.entries to deal with this specific situation, don't use MUI in the boundary), but I feel like this is something that should be solvable, and I was wondering if anyone can tell me the very obvious thing I'm doing wrong. We are rendering client-side, no server rendering at all. https://codesandbox.io/s/objective-dust-umpr1c
2 replies
TTCTheo's Typesafe Cult
Created by Roren on 11/14/2022 in #questions
Benchmarking slow LCP and TTI
26 replies
TTCTheo's Typesafe Cult
Created by Roren on 10/21/2022 in #questions
TypeScript adoption at work
I'm a more junior developer with just about three years of experience. Recently, we were given the go-ahead to start using TypeScript for a newer more green field project to test the waters. I think it's been a great experience, and has given me more confidence in the robustness of my code, and in the safety of my refactoring. Currently, something of a round table is happening where devs from various teams are being asked about whether we should use TS for a shared component library that is being started. There's some pushback from some of the more senior developers, in particular one who stated "The kinds of errors that type safety is intended to assist in the prevention of are generally avoidable with good coding practices.". Obviously I'm not going to die on this hill. I'm perfectly fine with the conclusion that different teams can make the decision for themselves, and that it should not be enforced necessarily. However, being a bit newer, I want to make sure I'm accurately presenting the biggest advantages. Does anyone have any advice on how to respond to that sentiment, or the sorts of things I can bring up in support? Thanks, have a great day! EDIT: I'd also be very interested in hearing some potential reasons NOT to use it. I want to develop and be able to really understand the consequences, rather than trying to be dogmatic. I've brought up the need for people unfamiliar with TS to learn, slow compile times, and potential frustration with errors (which sorta goes hand in hand with the first point), but I'm not sure if there's any other very common reasons people avoid it and would love to get some perspective.
66 replies
TTCTheo's Typesafe Cult
Created by Roren on 10/15/2022 in #questions
SSR to help mitigate slow client data transfer and improve TTI?
I'm involved in an ongoing saga at work to improve the state of a SPA that is just an order form. This app was built with CRA before I started working here, and we are rendering on the client-side, fetching data via GQL (7-ish requests in total), then doing a bit of processing of that data before setting in state and allowing full interaction. I've had some success moving the app away from using Context for everything and triggering tons and tons of updates, and also restructuring the way in which we are fetching data. However, I'm still not fully happy with the performance of the app on mobile. I've been told that we do have users that may be on slower phones and with connections as slow as 2G worldwide, and so I'm looking to see how I can potential mitigate some of the issues a user may run into. Please forgive me if this is a super basic question, as I'm not incredibly familiar with SSR/Next in practice and I could just have a fundamental misunderstanding. But it struck me that if a big area of concern is the potential slow speeds of CPU/network on the client side, it might make sense to send off for the data necessary to allow interaction on the server-side with something like getServerSideProps. This data is not consistent and varies depending on the URL's query string, so unfortunately generating something static and rebuilding doesn't strike me as feasible. If we were to send requests on the server, then we would not be beholden to the client's network speeds for this data transfer. However, I'm not sure if there's a trade-off I'm unaware of. I'd assume the transfer of HTML would be much lighter than some of the data we're fetching, but would we potentially have to pay a tax during hydration that will end up effectively meaning it takes just as long to load, but that we lose the expeditated TTFB we get from client-side rendering? Just wanted to check if this thinking makes sense, and if it's something I should prototype. Thanks, have a great day!
11 replies
TTCTheo's Typesafe Cult
Created by Roren on 10/12/2022 in #questions
Union TS generic function definitions into one type with all possible input -- output types
Working with GQL for my employer and using graphql-hooks which is being invoked inside a custom hook that handles adding headers for us as needed. I'd like to create a type definition that will allow us to denote what the expected response in the data key will be based on the input string passed as an argument. I went ahead and created a def that accepts two generic parameters, one which represents the input string literal (T) and the other which represents the expected shape of the data (U):
type UseGQLGeneric<T, U> = (
request: T, // GQL query string
requestOptions?: object
) => [RequestFn<U>, UseClientRequestResult<U>]; // U being the response body
type UseGQLGeneric<T, U> = (
request: T, // GQL query string
requestOptions?: object
) => [RequestFn<U>, UseClientRequestResult<U>]; // U being the response body
The idea being that you can import the string literal query being passed, and create a definition which types out the logical relationship between input and output, like:
type LoginQuery = UseGQLGeneric<typeof LOGIN_QUERY, { success: boolean }>;
type LoginQuery = UseGQLGeneric<typeof LOGIN_QUERY, { success: boolean }>;
I'd like to then union these definitions into one def that the function being invoked can be typed as, and accept any query string and return the corresponding value., e.g.
type UseGQLFunc = LoginQuery | OtherQuery | ThirdQuery;
type UseGQLFunc = LoginQuery | OtherQuery | ThirdQuery;
However, I'm running into an issue because it seems the argument being typed as T is always coming up as never in the final definition. Presumably because TS looks at two distinct string literals, sees that there is no way to union these types, and so assigns a value of never. Is there any way to achieve what I'm looking for, and type the function such that it dynamically returns the correct type based on the argument passed? Or will we instead need to cast each individual invocation, or handle it some other way? Sorry if something similar has been asked before, or if I'm being vague or unreasonable with what I'm asking. I hope you're all having a great day.
5 replies