Skip to content

Basics

Here is how to start a full React‑Redux application in just a few lines with scalux, using a minimal counter example.

On this page you will learn how to:

  • define an application's initial state
  • connect a component to that state
  • build the Redux store.

The counter

The example below shows how to set up a counter with:

  • the initial state generated via State
  • a connected component generated by Component
  • the assembly of the Redux store with register and configureStore.

Step 1 : Defining the initial state and connecting the components

tsx
// src/app.tsx
import { State } from "scalux";

// Create the initial state and generate the builders
// The state must be an object (not a primitive value)
const { Component, register } = State({ count: 0 });

// Display component definition
type CounterProps = {
  value: number;
  increment: () => void;
  decrement: () => void;
};

const Counter = ({ value, increment, decrement }: CounterProps) => (
  <div>
    <div>
      <button aria-label="Increment value" onClick={() => increment()}>
        Increment
      </button>
      <span>{value}</span>
      <button aria-label="Decrement value" onClick={() => decrement()}>
        Decrement
      </button>
    </div>
  </div>
);

// Assemble the connected component
const CounterComponent: React.FC = Component({
  domain: "Counter", // Action type prefix (e.g. "Counter/increment")
  render: Counter,
  data: (state) => ({ value: state.count }),
  handlers: {
    increment: (state) => ({ count: state.count + 1 }),
    decrement: (state) => ({ count: state.count - 1 }),
  },
});

// Register the handlers in Redux and generate the reducer.
// register must be called once all components have been defined.
const { reducer } = register();

export { CounterComponent, reducer };

Step 2 : Creating the store

typescript
// src/store.ts
import { reducer } from "./app";
// scalux re‑exports redux‑toolkit’s configureStore
import { configureStore } from "scalux";
// Example of adding custom middleware
import { userDefinedMiddleware } from "./middlewares";

export const store = configureStore({
  reducer,
  middleware: (getDefaultMiddleWare) => getDefaultMiddleWare(),
});

Step 3 : Using the component in the application

tsx
// src/main.tsx

// Re‑export of react‑redux’s Provider;
import ReactDOM from "react-dom/client";
import { Provider } from "scalux";
import { CounterComponent } from "./components";
import { store } from "./store";

ReactDOM.createRoot(
  document.getElementById("root").render(
    <Provider store={store}>
      <CounterComponent />
    </Provider>
  )
);

The remainder of this page explains each of these concepts in detail.

State

  • State, exported by scalux, defines the initial state from a serialisable data object.
  • The type of the state is inferred, and type annotations can be added to handle more complex situations (e.g. union types).

Component

The Component constructor returned by State connects a component to the application state.

It takes a configuration object with the following properties:

  • domain – A string used as a prefix for the generated action types. No action types have to be declared manually.

    Example: The handlers increment and decrement generate the action types Counter/increment and Counter/decrement respectively.

    Note: The domain must be unique within the application. A duplicate will not go unnoticed: it triggers an initialisation error with a debug message to help pinpoint the source.

  • render – A functional React component whose prop types are essential.

    scalux separates the component’s props by inference into two categories:

    • HandlerProps of type
      typescript
      (payload: SomePayload) => void | () => void
      handled by the handlers property of Component.
    • DataProps – everything else, handled by the data property of Component.
  • data – Receives a selector whose first parameter is the state (typed by inference) and returns the DataProps:

    typescript
    (state: RootState) => DataProps;
  • handlers – For each HandlerProp receives a state‑mutation function (typed by inference) whose first parameter is the state, the second an optional Payload, and which returns an object containing the state updates:

    typescript
    (state: RootState, payload: HandlerPayload) => Partial<InternalState>,

    In the example:

    • The handlers increment and decrement update the count property.
    • Their payloads are undefined (because the handlers are of type () => void).
    • InternalState is equal to RootState here; they diverge in advanced cases (e.g. history management), but the distinction will matter later.

    Each handler returns a subset of the internal state containing only the modified properties.
    If we had a handler prop { doNothing: () => void } then { doNothing: () => ({}) } would be valid.

register

The register function registers all reducers generated by the component handlers.

⚠️ Important

  • Call register after all your connected components are defined via Component.
  • One (optional) way to ensure this is to call register from a file that re‑exports all components.
  • The reducer returned is then used to configure the Redux store.

For example:

typescript
// register all the handlers defined previously
const { reducer } = register();

// export the reducer to build the store
export { CounterComponent, reducer };

Wrap‑up

In this section we have seen how to:

define a minimalist state via State

✅ declare a component to render the model by separating the data (DataProps) from the handlers (HandlerProps)

connect a component to the state and define its action types by assembling the render component with:

  • a domain
  • a data selector
  • handlers

This simple approach covers common use cases. For more complex applications, scalux provides advanced tools that let you:

  • split the state into slices, manage history and model state machines
  • feed components with various kinds of selectors: hard‑coded data, selectors generated from the initial state, memoisation techniques, management of ownProps
  • expose externalisation methods to avoid concentrating too many handlers in a single object.