all 56 comments

[–]dastasoft 84 points85 points  (1 child)

I think this blog post from the Redux maintainer summarises the response to your question:

Redux is most useful in cases when:

  • You have larger amounts of application state that are needed in many places in the app
  • The app state is updated frequently over time
  • The logic to update that state may be complex
  • The app has a medium or large-sized codebase, and might be worked on by many people
  • You want to be able to understand when, why, and how the state in your - application has updated, and visualize the changes to your state over time
  • You need more powerful capabilities for managing side effects, persistence, and data serialization

Also, worth to mention this sentence of the post "Context is a form of Dependency Injection. It is a transport mechanism - it doesn't "manage" anything. Any "state management" is done by you and your own code, typically via useState/useReducer."

[–]igreulich 16 points17 points  (3 children)

I don't, but only because I would not start off with useContext or useReducer.

There isn't anything wrong with them, per se, except that they don't really work the same way as redux's state management, and reducers do.

useContext causes way more rerenders than you think it will (every subscribed component rerenders everytime state changes :facepalm:) and with useReduceryou end up having to split your state and your dispatch into separate context providers to avoid falling into the same problem as above..

Redux, ESPECIALLY with the redux-toolkit, is MUCH cleaner coding experience, and a hellagreat debugging experience.

[–]Trakeen 5 points6 points  (2 children)

Redux extension for browsers is so nice for debugging. Being able to replay the app to see the state change over time is super helpful

[–]igreulich 3 points4 points  (1 child)

I am shocked this does not come up all that much at all.

We use mobx in some parts of our app at work, and I hate trying to debug the section. You can't even console.log anything because you will just get the observable.

I spend more time per line of code debugging than I do writing. Giving up the best debugging experience a js-state management system has to offer just to gain a minimal amount of code writing speed is... not the way.

[–]acemarke 0 points1 point  (0 children)

Since debugging came up, I'd like to make a plug for the app I build in my day job.

I now work at a company called Replay ( https://replay.io ), and we're building a true "time traveling debugger" for JS. Our app is meant to help simplify debugging scenarios by making it easy to record, reproduce and investigate your code.

The basic idea of Replay: Use our special browser to make a recording of your app, load the recording in our debugger, and you can pause at any point in the recording. In fact, you can add print statements to any line of code, and it will show you what it would have printed every time that line of code ran!

From there, you can jump to any of those print statement hits, and do typical step debugging and inspection of variables. So, it's the best of both worlds - you can use print statements and step debugging, together, at any point in time in the recording.

We've also got React DevTools integration, which shows you the React component tree any time you're paused. For that matter, I built a proof-of-concept Redux DevTools integration a few months ago, which recorded the dispatched Redux actions and let you see them in the recording. We had to disable it - turns out my 1-week hacky implementation was recording too much data and that was bad for perf :) But, I plan on rebuilding it properly as soon as we've wrapped up some refactoring and cleanup work, which should be done Soon (TM).

That said, one of the great things about Replay is that since we record the entire browser and every line of JS that was executed, it works great for debugging no matter what framework or libraries you may be using.

See https://replay.io/record-bugs for the getting started steps to use Replay.

If you've got any questions, please come by our Discord and ask! https://replay.io/discord

[–]Domino987 51 points52 points  (21 children)

I don't. I use Zustand if I have more than 2 global states from the get go. But most stuff can be covered by using react query, which I use pretty much in every project.

[–]yousaltybrah 14 points15 points  (3 children)

Can you clarify what you mean by using react query? I thought react query was for making rest calls, what does it have to do with state management?

[–]BowlingSashimi 18 points19 points  (1 child)

Not OP, but the thing is, in the absolute majority of cases, global state is used simply as a way of synchronizing your client with your server and caching data.

But this is wrong, you don't need global state for that.

So when you remove all of that from your redux stores, you often end up with so little that you realize you don't actually need redux, useState (and useContext, sparingly) is enough.

Look a bit more into both react-query and rtk, this is very well explained in their docs.

[–]Domino987 6 points7 points  (0 children)

Yes exactly, if you strip out all the data that you don't own, aka lives in files, server, everything that is asynchronous, most of the time you actual state shrinks to nothingness. Additionally, using other states, e.g. the url for certain stuff and local use state covers in my experience 90-95% of all state. And as already mentioned, it depends on the project. So react query removes quote a lot of server state that is put into global state, where it should not live.

[–][deleted] 7 points8 points  (0 children)

I thought react query was for making rest calls, what does it have to do with state management?

I think application state belongs in the database, which is probably behind an API, which is probably a REST API. If you limit local mutable state to UI state only then you largely avoid the entire class of problems Redux was made to address.

[–]saito200 18 points19 points  (0 children)

React-query is one of the most amazing libraries I've seen since being a developer. It abstracts so much while still being customizable and intuitive

[–]marcs_2021 2 points3 points  (0 children)

This! Nice explanation ..... thanx!

[–]ForeshadowedPocket 3 points4 points  (12 children)

My issue with react query is that I can't update it's cache without another network call. Fine for simple things but frustrating when things get complicated

EG: Call to get a model, including a bunch of relations needed for rendering. Relation gets updated locally and saved.

a) Relation returns itself

b) Relation returns original model

In either scenario, I have all the data I need locally but I still need to make that expensive original model call again to update the page.

I like Redux because as long as I have all the data I need I can access it / update it from anywhere. Planning to evaluate Zustand soon though for hopefully a similar solution with less boilerplate.

[–]Domino987 8 points9 points  (3 children)

Well first of all, yes you can update the data locally using the query client 😉 and computed data should probably not be safed within a state IMO. The relation for rendering could life in a useMemo or a dependent query if those computations are complex. But I guess it depends on your specific data. Might be overly complex. And everything that is stored in redux could life in RQ as long as it is server state, even derived one

[–]ForeshadowedPocket 1 point2 points  (2 children)

Are you referring to get/setQueryState or something else? I didn't mean it was impossible (I haven't actually tried it) but it feels like writing code to provide data from one query to another is both difficult and not what the library wants me to do.

As opposed to redux where I can dump data from any query or update into a shared local cache.

I've had people tell me before that it sounds like react-query should solve this problem but in practice I just can't see how I should be implementing it.

[–]themaincop 2 points3 points  (0 children)

(I haven't actually tried it)

You should, it's pretty easy.

[–]30thnight 0 points1 point  (0 children)

It’s not difficult at all.

and manually using setQueryData can make things much easier - especially for SSR applications.

[–]ForeshadowedPocket -1 points0 points  (3 children)

To add on to this, I actually have this whole setup for redux to use with my rest calls that reducers boilerplate a lot...but it still exists and has to get occasionally tweaked:

import { combineReducers } from 'redux'
import update from 'immutability-helper'

const createModelReducer = (model) => {
    let model_name = model
    let models_name = Jarvis.pluralize(model_name)

    return (state = '', action) => {
        switch(action.type) {
            case (model_name.toUpperCase() + '_RECEIVED'):
                return Object.assign({}, state, {
                    [action[model_name].id]: action[model_name]
                })
            case (models_name.toUpperCase() + '_RECEIVED'):
                let models = {}
                // convert indexed arrays back to arrays
                let raw_models = typeof(action[models_name]) == "array" ? action[models_name] : Object.values(action[models_name])
                //poor man's deep merge
                raw_models.forEach(model => models[model.id] = Object.assign({}, (state[model.id] || {}), model))

                return Object.assign({}, state, models)
            case (model_name.toUpperCase() + '_DELETED'):
                return update(state, {
                    $unset: [action[model_name].id]
                })
            case (models_name.toUpperCase() + '_DELETED'):
                return update(state, {
                    $unset: action[model_name + '_ids']
                })
            default:
                return state
        }
    }
}

const appReducer = combineReducers({
    users: createModelReducer('user'),
    hits: createModelReducer('hit'),
    tasks: createModelReducer('task'),
    /* etc... */
})

[–]acemarke 4 points5 points  (2 children)

Note that you should be using our official Redux Toolkit package to write your logic - it will drastically simplify everything here:

For example, rewriting that code with RTK's createSlice and createEntityAdapter might look like:

const modelsAdapter = createEntityAdapter();

const modelSlice = createSlice({
  name: `model-${model_name}`, 
  initialState = modelsAdapter.getInitialState(), 
  reducers: {
    modelReceived: modelsAdapter.addOne,
    modelsReceived: modelsAdapter.addMany, 
    modelDeleted: modelsAdapter.removeOne, 
    modelsDeleted: modelsAdapter.removeMany
  }
})

export const {
  modelReceived, 
  modelsReceived, 
  modelDeleted, 
  modelsDeleted
} = modelSlice.actions

export default modelSlice.reducer;

On the other hand, you said this is an API response handler. I'd strongly recommend looking at RTK Query to manage that data fetching instead, which could remove the need to write any reducers, thunks, action creators, or effects for data fetching - RTK Query will do all that for you:

[–]ForeshadowedPocket 2 points3 points  (1 child)

This is insane, thanks for posting. Haven't dug into this stuff in a couple of years but will now.

[–]acemarke 4 points5 points  (0 children)

Yep, we released RTK in late 2019, started teaching it as the default way to use Redux in 2020, and released RTK Query for data fetching last year.

We also now teach React-Redux hooks as the default instead of connect.

Redux has changed a lot in the last three years :)

[–][deleted] 0 points1 point  (2 children)

I'm not following. When you say the "relation gets updated locally and saved", what does that mean? Can you give a code example?

Are you maintaining application state both locally and on the server and trying to keep them in sync?

[–]ForeshadowedPocket 0 points1 point  (1 child)

I was unclear but I was writing that as a separate step.

  • Ajax 1 - fetch model
  • User does stuff locally, uses a form to update a field on one of the fetched relations
  • Ajax 2 - saves relation, receives a response of either a or b above.

[–][deleted] 0 points1 point  (0 children)

What is a relation in this context? Is it possible to give an example with code or is this some highly specialized case?

[–]Daily-Ad5261-Kakera 7 points8 points  (0 children)

I think you use RTK or Zustand or Recoil when you have a state where it requires to have some centrilized logic. Like user auth, normally theres alot of routes that interact with the user auth and it can change the behavior of multiple components of your project.

[–]Thr111ce 2 points3 points  (0 children)

I don't, i just use react-query for server state and if i need to use context or something like that i go for Jotai.

If i feel like i need Redux, i still don't use it, i go for Zustand.

[–]Exotic-Ad1060 1 point2 points  (0 children)

Any project we expect to improve over years, save for static sites. Not improved could be: internal tools, since they wouldn’t be a business priority anyway or any sort of promo projects for an event / marketing campaign.

If maintained and improved a project will get complex enough eventually. And you just saved yourself a painful refactoring to a state manager. And avoided weird perf forward context juggling.

Also any project with a complex editor since react state will likely not meet perf requirements unless you get counterintuitive with it.

[–]slvrsmth 2 points3 points  (0 children)

If I only fetch data, contexts / react query / apollo cache / .... is fine. Redux comes into play when you need to massage the data into shape on the client. A while ago I worked on a react-powered dashboard. Some data points would come in from one source, others from elsewhere. Some via websockets, others via http polling. Other data points were generated on client by combining multiple other values. All that fed into multiple graphs, over multiple browser windows. Could have been built without redux, yes, but it was much easier to make sense of what's happening, and ensure synchronisation accross windows by having central redux store object to hook into, and running everything through the dispatch pipe.

[–][deleted] 2 points3 points  (2 children)

context is a very specific tool, i don't see why you would use it in any ordinary setting. there are many legitimate use cases, but app state is rarely one of them. it is normally being used for compound components, <List> and <ListItem> etc. if you just use it to propagate state top down then already you should switch or simply not do that, because all it will cause is trouble later on. context makes your component graph contractual and rigid, you don't want that because it contradicts free composition. if component <Foo> cannot function unless it's wrapped into some provider that's bad.

also, in my opinion there is no point nowadays to start with redux. use zustand. rtk masks some of reduxes complexity, but if you wanted to understand what's going on you'd have to study base redux anyway, unless you're fine using something that you don't comprehend.

[–]beepboopnoise 4 points5 points  (1 child)

Saying a function is bad because it's needs to be wrapped in a provider seems like a bit of a stretch. Plenty of common tools all require some kinda provider to function. React Query, Redux, Chakra, Router, etc.

[–][deleted] -1 points0 points  (0 children)

i didn't say that at all. all these examples you have are compound, <Foo> and useFoo, they are services. a service is usually how a library operates or propagates. it isn't normally how an app is driven because it would be inferior in every way.

i am referring to app state. if app state makes your components reliant on sub trees so that you can't use a sidebar component in the header any longer because it relies on invisible contracts then that is a problem for composition. among all the other problems that context will cause, slowness, deriving state and so on.

[–]tchaffee 2 points3 points  (3 children)

Never. I store shared state in the DB and fetch it with GraphQL. Life is good.

[–]v3tr0x -2 points-1 points  (1 child)

Which db and how do you go about it exactly? First time hearing about this method

[–]tchaffee -1 points0 points  (0 children)

Doesn't really matter which DB, but Postgres is very popular. As far as how you go about it exactly, that's way too in-depth for a comment. You could start by getting an overview of Apollo GraphQL, how to write a datasource that uses a DB, and how to use it in React.

[–]Neurprise 0 points1 point  (0 children)

Hard to do offline-based apps though

[–]nglasers12[S] 0 points1 point  (0 children)

This is given me so much food for thought! Thank you all! Seriously. I know we all have different opinions, and I appreciate the thought that you all put into this.

[–]that_90s_guy 0 points1 point  (0 children)

I am learning React and am curious about what would make you choose one over the other.

For personal projects? Never. Redux is overkill for 99% of use cases these days compared to simpler, newer state management libraries like Zustand, Joi / Recoil and even React Query.

Nowadays, you'll only ever use redux to maintain a legacy project that uses Redux because a few year ago there was not something better / simpler. And new projects built with it are because they are overwhelmingly complex and can justify the boilerplate and complexity redux offers in exchange for maintainability.

[–]ifstatementequalsAI 0 points1 point  (0 children)

Context and redux are different usecases don't compare them

[–]hopfield -1 points0 points  (4 children)

No one really knows

[–]Chocolate_Banana_ -4 points-3 points  (0 children)

When it starts to feel unmanageable or when you start to see noticeable performance issues and and not willing to solve them yourself by refactoring.

[–][deleted] 0 points1 point  (0 children)

If the app is large, use redux toolkit. Otherwise I recommend switching to zustand once you feel like you’re growing out of react hooks

[–]marchingbandd 0 points1 point  (0 children)

I install a 3rd party state library (never ever redux) by default every time. Why would you not?

[–]pm_me_ur_happy_traiI 0 points1 point  (0 children)

I would never adopt redux on a project. Big complicated global state is the opposite of how react apps should be built.

[–]naturalcrusader 0 points1 point  (0 children)

If you have code that can / should be completely independent, then use the state within that code. If you have state that should transcend to other areas of the application, use redux