easy-peasy icon indicating copy to clipboard operation
easy-peasy copied to clipboard

Documentation make no sense. (Typescript)

Open apuatcfbd opened this issue 2 years ago • 2 comments

Followed this: https://easy-peasy.vercel.app/docs/tutorials/typescript.html

then end-up like this

interface Todo {
    text: string;
    done: boolean;
}

interface StoreModel {
    todos: Todo[];
}

interface TodosModel {
    todos: Todo[];
    completedTodos: Computed<TodosModel, Todo[]>;
    addTodo: Action<TodosModel, Todo>;
    saveTodo: Thunk<TodosModel, Todo>;
}

const store = createStore<StoreModel>({
    todos: [],
    completedTodos: computed((state) => state.todos.filter((todo) => todo.done)),
    addTodo: action((state, payload) => {
        state.todos.push(payload);
    }),
    saveTodo: thunk(async (actions, payload) => {
        const result = await axios.post('/todos', payload);
        actions.addTodo(result.data);
    }),
});

export default store;

which look like this https://prnt.sc/C-FuQtxZDvpU then: https://prnt.sc/ZO6_XK27F_D-

I still can't figure out what is the issue. I see one issue is the documentation.

** "easy-peasy": "^5.0.4" "typescript": "^4.6.4" on PHPStorm 2022.1

apuatcfbd avatar Sep 13 '22 10:09 apuatcfbd

Yeah, this is bad documentation. Soz.

ctrlplusb avatar Sep 15 '22 14:09 ctrlplusb

@apuatcfbd If you're going to have multiple "slices" in your store, you can do something like this:

interface Todo {
  text: string;
  done: boolean;
}

interface StoreModel {
  todos: TodosModel;
  // ...another slice here
}

interface TodosModel {
  todos: Todo[];
  completedTodos: Computed<TodosModel, Todo[]>;
  addTodo: Action<TodosModel, Todo>;
  saveTodo: Thunk<TodosModel, Todo>;
}

const store = createStore<StoreModel>({
  todos: {
    todos: [],
    completedTodos: computed((state) => state.todos.filter((todo) => todo.done)),
    addTodo: action((state, payload) => {
        state.todos.push(payload);
    }),
    saveTodo: thunk(async (actions, payload) => {
        const result = await axios.post('/todos', payload);
        actions.addTodo(result.data);
    }),
  }
  // ...another slice here
});

Or if your store is going to be flat:

interface Todo {
  text: string;
  done: boolean;
}

interface StoreModel {
  todos: Todo[];
  completedTodos: Computed<StoreModel, Todo[]>;
  addTodo: Action<StoreModel, Todo>;
  saveTodo: Thunk<StoreModel, Todo>;
}

const store = createStore<StoreModel>({
  todos: [],
  completedTodos: computed((state) => state.todos.filter((todo) => todo.done)),
  addTodo: action((state, payload) => {
    state.todos.push(payload);
  }),
  saveTodo: thunk(async (actions, payload) => {
    const result = await axios.post("/todos", payload);
    actions.addTodo(result.data);
  })
});

Either setup should fix your type errors.

no-stack-dub-sack avatar Sep 15 '22 18:09 no-stack-dub-sack

Attempted to clarify the TS example in #770

jmyrland avatar Sep 16 '22 12:09 jmyrland

@apuatcfbd If you're going to have multiple "slices" in your store, you can do something like this:

interface Todo {
  text: string;
  done: boolean;
}

interface StoreModel {
  todos: TodosModel;
  // ...another slice here
}

interface TodosModel {
  todos: Todo[];
  completedTodos: Computed<TodosModel, Todo[]>;
  addTodo: Action<TodosModel, Todo>;
  saveTodo: Thunk<TodosModel, Todo>;
}

const store = createStore<StoreModel>({
  todos: {
    todos: [],
    completedTodos: computed((state) => state.todos.filter((todo) => todo.done)),
    addTodo: action((state, payload) => {
        state.todos.push(payload);
    }),
    saveTodo: thunk(async (actions, payload) => {
        const result = await axios.post('/todos', payload);
        actions.addTodo(result.data);
    }),
  }
  // ...another slice here
});

Or if your store is going to be flat:

interface Todo {
  text: string;
  done: boolean;
}

interface StoreModel {
  todos: Todo[];
  completedTodos: Computed<StoreModel, Todo[]>;
  addTodo: Action<StoreModel, Todo>;
  saveTodo: Thunk<StoreModel, Todo>;
}

const store = createStore<StoreModel>({
  todos: [],
  completedTodos: computed((state) => state.todos.filter((todo) => todo.done)),
  addTodo: action((state, payload) => {
    state.todos.push(payload);
  }),
  saveTodo: thunk(async (actions, payload) => {
    const result = await axios.post("/todos", payload);
    actions.addTodo(result.data);
  })
});

Either setup should fix your type errors.

Need slices in different file, there're many of them

apuatcfbd avatar Sep 16 '22 15:09 apuatcfbd

@apuatcfbd Have you figured out how to setup slices in different files? This is possible. It's also possible to define each thunk, action, computed prop, etc. in a different file if you prefer that kind of organization. If you'd like an example of how to do slices this way, let me know and I can share an example.

no-stack-dub-sack avatar Sep 20 '22 19:09 no-stack-dub-sack

@apuatcfbd Have you figured out how to setup slices in different files? This is possible. It's also possible to define each thunk, action, computed prop, etc. in a different file if you prefer that kind of organization. If you'd like an example of how to do slices this way, let me know and I can share an example.

We're open to PRs for more examples, so feel free to chuck one in there 👍

I'm currently working on adding some more (#773)

jmyrland avatar Sep 20 '22 20:09 jmyrland

@apuatcfbd Have you figured out how to setup slices in different files? This is possible. It's also possible to define each thunk, action, computed prop, etc. in a different file if you prefer that kind of organization. If you'd like an example of how to do slices this way, let me know and I can share an example.

We're open to PRs for more examples, so feel free to chuck one in there 👍

I'm currently working on adding some more (#773)

An example would be helpful for me & others too.

apuatcfbd avatar Sep 21 '22 13:09 apuatcfbd

@jmyrland Ok, cool - I can work on one for multiple slices and how to organize across several files. May take a bit for me to put it together but I'll def get on it.

In the meantime @apuatcfbd, consider this folder structure:

image

For this, we can set up our slices and our store like this:

// app/slices/todo/index.ts
import * as actions from "./actions";
import * as computed from "./computed";
import * as thunks from "./thunks";

export interface Todo {
  text: string;
  done: boolean;
}

type State = {
  todos: Todo[]
}

const initialState: State = {
  todos: []
};

const todoModel: TodoModel = {
  ...initialState,
  ...actions,
  ...thunks,
  ...computed,
};

export type TodoState = State;

export type TodoModel = TodoState &
  typeof actions &
  typeof thunks &
  typeof computed;

export default todoModel;
// app/store.ts
import { State, createStore, createTypedHooks } from "easy-peasy";
import todo, { TodoModel } from "./slices/todo";

export type StoreModel = {
  todo: TodoModel;
  // ...more slices here
};

export type RootState = State<StoreModel>;

export const { useStoreActions, useStoreState } =
  createTypedHooks<StoreModel>();

const store = createStore<StoreModel, RootState, Dependencies>({
    todo,
    // ... more slices here
});

export default store;

And in actions, thunks, computed, etc.:

// app/slices/todo/actions.ts
import { Action, action } from "easy-peasy";
import { Todo, TodoModel } from ".";

export const addTodo: Action<TodoModel, Todo> = action((state, payload) => {
  state.todos.push(payload);
});

// ...more actions

Let me know if this makes sense. I will try to throw together a codesandbox at some point. You can also go a step further if you have many actions / thunks / computed props and put them all in individual files and export them all from an index file (e.g. actions becomes a directory with an index file and all actions in the directory are exported from there so you can still do import * as actions from "./actions"; and get the type required for slice model).

@jmyrland Sounds like you have lots of easy-peasy apps in prod, do you have a different approach or do you mostly have flat stores or slices defined in a single file?

no-stack-dub-sack avatar Sep 22 '22 20:09 no-stack-dub-sack

@jmyrland Ok, cool - I can work on one for multiple slices and how to organize across several files. May take a bit for me to put it together but I'll def get on it.

That would be awesome. Take a look at #773 if you need inspiration or a project template.

Let me know if this makes sense. I will try to throw together a codesandbox at some point. You can also go a step further if you have many actions / thunks / computed props and put them all in individual files and export them all from an index file (e.g. actions becomes a directory with an index file and all actions in the directory are exported from there so you can still do import * as actions from "./actions"; and get the type required for slice model).

Nice @no-stack-dub-sack ! Your examples look really neat 👍 I think this setup would work great, especially for big slices/stores.

@jmyrland Sounds like you have lots of easy-peasy apps in prod, do you have a different approach or do you mostly have flat stores or slices defined in a single file?

We use multiple slices (we call them nested stores/sub-stores), even up to 3-4 levels deep. Our slices are primarily in a single file - as the slices themselves aren't that big.

Because of our usecase, we treat models for each section a bit like "css modules" - we want them to live along side the component. So typically we have a Component.tsx and a Component.model.ts for the store model related to the component. This basically means that our file structure basically mirrors our state object.

All these component models are then nested into slices and referenced in our main model.ts.

That's the gist of it. It's a bit tricky to exemplify, but I'll think of some way to make an example of this when I got some spare time.

jmyrland avatar Sep 22 '22 21:09 jmyrland

Btw, this model uses slices/nested stores & store generators.

jmyrland avatar Sep 22 '22 21:09 jmyrland

Nice @no-stack-dub-sack ! Your examples look really neat 👍 I think this setup would work great, especially for big slices/stores.

@jmyrland Thanks, yeah, we've found that this works well for us and our use-case. In our approach our components all generally map to a use-case / functionality represented by a logical state slice.

Because of our usecase, we treat models for each section a bit like "css modules"...

Sounds like an interesting approach! I suppose this is all closed source? When you say sub-store, though, you still only have one top-level call to createStore, like in the example you linked to right? i.e. you can still access all of your nested models via a single useStoreState hook.

no-stack-dub-sack avatar Sep 23 '22 00:09 no-stack-dub-sack

Also, I initially thought you meant adding an additional section the docs tutorial section, but now I see you mean adding an example project. This may take me a bit more time to put together but I will still keep this on my todo list.

no-stack-dub-sack avatar Sep 23 '22 00:09 no-stack-dub-sack

When you say sub-store, though, you still only have one top-level call to createStore, like in the example you linked to right? i.e. you can still access all of your nested models via a single useStoreState hook.

Correct 👍

Also, I initially thought you meant adding an additional section the docs tutorial section, but now I see you mean adding an example project. This may take me a bit more time to put together but I will still keep this on my todo list.

Oh I see 😅 sorry for the confusion.

Any help is appreciated 👏

jmyrland avatar Sep 23 '22 05:09 jmyrland