Understanding effects
It is finally time to dive deep into Chonky's most powerful feature - file action
effects. "Effect" in this context means a "side effect" of the action. It has nothing
to do with React's useEffect
.
Please note that action effects are an advanced feature. As such, using them effectively requires a fair bit of Redux knowledge and understanding Chonky's internal logic.
Why are effects useful?
On the Defining custom actions page, you saw how you could change some basic parts of Chonky's internal state, e.g. the current sorting function or the file selection. While the mechanisms presented on that page are useful, they only cover roughly 10% of Chonky's internal Redux state.
There's no doubt advanced users will want to read or set other parts of Chonky's Redux state. At the same time, it is clearly impractical to provide explicit mechanisms to alter every part of the state, because it will result in a highly fragile API that will be hard to maintain.
Enter action effects. They provide a generic mechanism for accessing and modifying Chonky's state by directly exposing its Redux state and Redux dispatch to developers. This creates many interesting opportunities, like completely changing the meaning of single & double clicks or chaining file actions. Unsurprisingly, many Chonky's built-in actions use effects to make things happen.
There is an obvious downside to this approach - to make full use of action effects, one should have a good understanding of what is stored in Chonky's Redux state, and what Redux actions and thunks are available. While I agree that this is inconvenient, I also think it is much better than the alternative, which is to fork Chonky on GitHub and rewrite parts of code yourself.
Action effect definition
Recall the defineFileAction
helper method from the Defining custom
actions page:
defineFileAction(action: FileAction, effect?: FileActionEffect) => FileAction;
The second parameter is what you use to define the effect. Formally, effects should
follow the FileActionEffect
type:
export type FileActionEffect<Action extends FileAction = any> = (data: {action: Action;payload: Action['__payloadType'];state: FileActionState<{}>; // extra state is empty on purposereduxDispatch: ChonkyDispatch;getReduxState: () => RootState;}) => MaybePromise<undefined | boolean | void>;
You should already know about action
, payload
and state
parameters from the
Defining an action handler page.
The new parameters are reduxDispatch
and getReduxState
. If you ever used
Redux with redux-thunk
middleware, you know exactly what they
do - reduxDispatch
lets you dispatch Redux actions or thunks to Chonky's Redux
store, and getReduxState
lets you get Chonky's internal Redux state.
Effect return value
Note that the effect function is executed as the last step in Chonky's action processing
pipeline, right before dispatching the action to the user-defined action handler. If
you want to prevent the action handler from being called, you can return true
from
your effect. Alternatively, you can return a promise that resolves into true
.
Example action effect
Consider the definition of Chonky's built-in OpenParentFolder
action:
import {ChonkyActions,ChonkyIconName,defineFileAction,FileHelper,thunkRequestFileAction,} from 'chonky';import { selectParentFolder } from 'chonky/lib/redux/selectors';const OpenParentFolder = defineFileAction({id: 'open_parent_folder',hotkeys: ['backspace'],button: {name: 'Go up a directory',toolbar: true,contextMenu: false,icon: ChonkyIconName.openParentFolder,iconOnly: true,},} as const,({ reduxDispatch, getReduxState }) => {const parentFolder = selectParentFolder(getReduxState());if (FileHelper.isOpenable(parentFolder)) {reduxDispatch(thunkRequestFileAction(ChonkyActions.OpenFiles, {targetFile: parentFolder,files: [parentFolder],}));}});
This action definition demonstrates two important concepts:
- It shows that you can use any of Chonky's built-in Redux selectors by importing them
from
chonky/lib/redux/selectors
. You can see the full list inselectors.ts
on GitHub. You can also define your own selectors if you want. - It shows that you can use the
thunkRequestFileAction
thunk to trigger actions from Chonky. The simplified method signature for this thunk looks like this:Note thatthunkRequestFileAction(action: FileAction,payload: FileAction['__payloadType']) => MaybePromise<void>;payload
is a required parameter. For actions that don't have a payload, you can just passundefined
.
Available resources for action effects
File action effects rely heavily on Redux actions and selectors, so it's very useful to understand how they work. There are many Redux resources available on Google, but you can start with Redux Overview from the official Redux docs.
It is also useful to know what Redux actions, selectors and thunks Chonky actually defines:
Chonky's Redux state:
redux.types.ts
: This file shows the Typescript interface for Chonky's Redux state.state.ts
: This file shows the initial state for Chonky's Redux store. Note that most of initial state is overwritten whenFileBrowser
component mounts, so don't rely on the contents of this file too much.
Redux selectors:
selectors.ts
: This file shows all of the built-in Redux selectors. Of course you are free to define your own selectors, or just access the state object directly.Example usage:
import { selectCleanFileIds } from 'chonky/lib/redux/selectors';defineFileAction({ id: 'selector_example_action' } as const,({ getReduxState }) => {const cleanFileIds = selectCleanFileIds(getReduxState());console.log('Non-null file IDs:', cleanFileIds);});
Redux thunks:
files.thunks.ts
: This file contains thunks related to different file operations - setting the new file array, sorting files, setting the current search string, etc.file-actions.thunks.ts
: This file contains thunks related to common file action operations - setting the current file view, applying a selection transform, toggling options, etc.dispatchers.thunks.ts
: This file contains thunks related to requesting and dispatching file actions.Example usage:
import { thunkUpdateSearchString } from 'chonky/lib/redux/thunks/files.thunks';defineFileAction({ id: 'thunks_example_action' } as const,({ reduxDispatch }) => {// Set search to all Photoshop files (.psd)reduxDispatch(thunkUpdateSearchString('.psd'));});
Redux actions:
reducers.ts
- This file exports thereduxActions
constant, which holds all of Chonky's Redux actions. You can see thatreducers
object defines a bunch of functions - you can call any of them as an action. You can read more aboutcreateSlice
method from Redux Toolkit to understand what is happening under the hood.Example usage:
import { reduxActions } from 'chonky/lib/redux/reducers';defineFileAction({ id: 'redux_actions_example_action' } as const,({ reduxDispatch }) => {// Disable selectionreduxDispatch(reduxActions.setSelectionDisabled(true));});
It is also useful for you to review the definitions of Chonky's essential built-in actions on GitHub. They define many different effects, and they are a good example of what action effects can do.
Questions and suggestions
If you have a question about action effects, or want to request an addition to Chonky's Redux state or Redux actions, you can create an issue on GitHub or join Chonky's Discord server.
Defining your own action effect
As you might know, you can use the disableSelection
prop on FileBrowser
to toggle
the file selection feature on and off. We will define a custom action that will allow
your users to toggle selection functionality from the UI (using file effects).
Try selecting some file in the file browser below then clicking on Disable selection
button.