Effector

Effector

  • Docs
  • Try
  • API
  • Blog
  • Twitter
  • GitHub

›Recipes

Introduction

  • Installation
  • Core Concepts
  • Prior Art

Recipes

  • Recipes: Index
  • Usage with TypeScript
  • Migrating to Effector

    • From Redux

    React

    • Example
    • Example: Effects
    • Example: Forms
    • Gate: a bridge between props and store
    • Example: TODO creator
    • Example: Dynamic form schema

    React Native

    • React Native Example

Other

  • Troubleshooting
  • Glossary

FAQ

  • FAQ
Edit

Usage with TypeScript

TypeScript is a typed superset of JavaScript. It has become popular recently in applications due to the benefits it can bring. If you are new to TypeScript it is highly recommended to become familiar with it first before proceeding. You can check out its documentation here.

TypeScript has the potential to bring the following benefits to application:

  1. Type safety for state, stores and events
  2. Easy refactoring of typed code
  3. A superior developer experience in a team environment

A Practical Example

We will be going through a simplistic chat application to demonstrate a possible approach to include static typing. This chat application will have two domains. The Chat domain will focus on working with the chat history and the system domain will focus on working with session information.

The full source code is available on codesandbox here. Note that by going through this example yourself you will experience some of the benefits of using TypeScript.

Type checking state

Adding types to each slice of state is a good place to start since it does not rely on other types. In this example we start by describing the chat store's slice of state:

// src/effector/chat/types.ts

export interface Message {
  id: string
  user: string
  message: string
  timestamp: number
}

export interface ChatState {
  messages: Message[]
}

And then do the same for the system store's slice of state:

// src/effector/system/types.ts

export interface SystemState {
  loggedIn: boolean
  session: string
  userName: string
}

Note that we are exporting these interfaces to reuse them later in stores and events.

Organize yous domains

To effectively structure the project code, you can break the application logic into separate domains that combine all the logic of working with this part of the application.

// src/effector/chat/domain.ts

export const ChatDomain = createDomain()
// src/effector/system/domain.ts

export const SystemDomain = createDomain()

Type checking events and effects

All events can be typed through their payload. Effects used to handle async reactions and events for sync reactions.

// src/effector/chat/events.ts

export const sendMessage = ChatDomain.effect<Message, Message, Error>()

export const deleteMessage = ChatDomain.effect<Message, Message, Error>()

Each effect must be provided with a handler function that will provide final processing. You can connect them at any time, so leave it for further action.

// src/effector/system/events.ts

export const updateSession = SystemDomain.event<SystemState>()

Type checking for stores

Keep yours stores as simple as possible. Let each store be responsible for its part of the state in the general state of the application or domain.

// src/effector/chat/store.ts

const initialState: Message[] = [
  {
    id: oid(),
    user: 'system',
    message: 'this message from initial state',
    timestamp: new Date().getTime(),
  },
]

export const MessageList = ChatDomain.store<Message[]>(initialState)
  .on(sendMessage.done, (state, {result}) => [...state, result])
  .on(deleteMessage.done, (state, {result}) =>
    state.filter(message => message.id !== result.id),
  )

The closest comparison for an event handler is a reducer that processes exactly one event. In this example, there is no need to declare the types with which the handler will be called, since the typescript has enough information to deduce all the necessary types, it can also guarantee the correctness of the return value from this handler.

// src/effector/system/store.ts

const initialState: SystemState = {
  loggedIn: false,
  session: '',
  userName: '',
}

export const SystemStore = SystemDomain.store<SystemState>(initialState).on(
  updateSession,
  (state, payload) => ({...state, ...payload}),
)

Usage with effector-react

While Effector React is a separate library from effector itself, it is commonly used with react. For this reason, we will go through how Effector React works with TypeScript using the same example used previously in this section.

Events and effects you can use directly in yours components and to get access to store data you can use useStore hook from effector-react package.

Let start with implement effect backend

// src/api/MessageApi.ts

export class MessageApi {
  public static sendMessage = (message: Message) =>
    new Promise<Message>(resolve => setTimeout(() => resolve(message), 2000))

  public static deleteMessage = (message: Message) =>
    new Promise<Message>(resolve => setTimeout(() => resolve(message), 2000))
}

Then bind created handlers to effects

// src/index.tsx

sendMessage.use(MessageApi.sendMessage)
deleteMessage.use(MessageApi.deleteMessage)

Then we can implement components which uses data from stores. And start operate with our effects.

// src/ChatHistory.tsx

export const ChatHistory: React.FC = () => {
  const messages = useStore(MessageList)

  return (
    <div className="chat-history">
      {messages.map(message => (
        <div className="message-item" key={message.timestamp}>
          <h3>From: {message.user}</h3>
          <p>{message.message}</p>
          <button onClick={() => deleteMessage(message)}>delete</button>
        </div>
      ))}
    </div>
  )
}

Also, close attention should be paid to the moment that the data from the local state can be transmitted to the final effect. For these purposes, the forward method is used. Here is an example of such use in the component.

// src/ChatInterface.tsx

const onSend = ChatDomain.event<string>()

forward({
  from: onSend.map<Message>(message => ({
    id: oid(),
    user: SystemStore.getState().userName,
    timestamp: new Date().getTime(),
    message,
  })),
  to: sendMessage,
})

const ChatInterface: React.FC = () => {
  const {userName} = useStore(SystemStore)

  const [message, updateMessage] = useState('')

  const keyPress = (e: React.KeyboardEvent<any>) => {
    if (e.key === 'Enter') {
      send()
    }
  }

  const send = () => onSend(message)

  return (
    <div className="chat-interface">
      <h3>User: {userName} </h3>
      <input
        value={message}
        onChange={e => updateMessage(e.target.value)}
        onKeyPress={keyPress}
        className="chat-input"
        placeholder="Type a message..."
      />
      <button onClick={send}>Send</button>
    </div>
  )
}
← Recipes: IndexFrom Redux →
  • A Practical Example
  • Type checking state
  • Organize yous domains
  • Type checking events and effects
  • Type checking for stores
  • Usage with effector-react
Effector
Docs
Getting StartedAPI Reference
Community
User ShowcaseStack OverflowGitterTwitter
More
GitHubStar
Copyright © 2019 zerobias