Reducers can drive effects, in fact they do it all the time via state changes which trigger UI rendering — arguably the application's most important effect.
Driving non-visual effects isn't as common but there are at least two solutions: redux-loop and redux-agent (which I wrote).
From the respective sites:
redux-loop:
A port of the Elm Architecture to Redux that allows you to sequence your effects naturally and purely by returning them from your reducers. https://redux-loop.js.org/
redux-agent:
Redux Agent extends React’s model to non-visual I/O: describe a network request, a storage operation, a websocket message, … and let the machine worry about performing it. Logic stays in the reducer, components stay lightweight, and it’s easy to see what state triggers which effect. https://redux-agent.org/
(The quotes also hint at a major difference between the two: redux-loop returns effect descriptions from the reducer, thereby changing its API and requiring a store enhancer. redux-agent works with vanilla Redux API.)