For simpler to moderate complexity, you might be able to get away with just the built-in useState and useContext hooks together.
You can wrap your whole app in a context provider, and/or combine it with state trees that only certain features or components or users need. Then any child within any tree can pull from the contexts.
It's up to you whether you want to use a reducer like pattern, or make the setter itself available in the context so any consumer can mutate state, or pass along a helper function that sets state as an abstraction.
Any way you do it, it will be far easier to use than the nightmare that is Redux. It might be more disorganized, with a lot of separate state hooks, but each one can be relatively modular. Overall I found it waaaaaay easier than Redux.
I like to use KeaJs, which is built on top of Redux
My favorite approach is Zustand, which is a simplistic object with data and functions to update that data, combined with react-query for doing a lot of the data fetching. Zustand is super simple and based around hooks, and I find that with a data fetching library that handles caching, I have a lot less “global” state than in the past. IMO for even relatively complex apps, having a ton of global state is not great, and breaking things down into specific contexts that are each separate hooks is great. I think Redux’s Toolkit or whatever it’s called works similarly.