Updated with more secure modelling on backend
This commit is contained in:
parent
4f3b19891a
commit
bb99f99a22
@ -44,8 +44,13 @@ public class TodosController : ApiController
|
|||||||
=> await Mediator.Send(new GetTodoByIdQuery(todoId));
|
=> await Mediator.Send(new GetTodoByIdQuery(todoId));
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<IEnumerable<Core.Entities.Todo>>> GetTodos()
|
public async Task<ActionResult<IEnumerable<TodoViewModel>>> GetTodos([FromQuery] bool onlyActive = false)
|
||||||
=> Ok(await _todoRepository.GetTodosAsync());
|
{
|
||||||
|
if (onlyActive)
|
||||||
|
return Ok(await Mediator.Send(new GetActiveTodosQuery()));
|
||||||
|
|
||||||
|
return Ok(await Mediator.Send(new GetTodosQuery()));
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPut("{todoId}")]
|
[HttpPut("{todoId}")]
|
||||||
public async Task<ActionResult> ReplaceTodo([FromRoute] string todoId, [FromBody] ReplaceTodoRequest request)
|
public async Task<ActionResult> ReplaceTodo([FromRoute] string todoId, [FromBody] ReplaceTodoRequest request)
|
||||||
@ -69,11 +74,15 @@ public class TodosController : ApiController
|
|||||||
[JsonPropertyName("status")]
|
[JsonPropertyName("status")]
|
||||||
public bool Status { get; init; }
|
public bool Status { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("created")]
|
||||||
|
public long Created { get; init; } = 0;
|
||||||
|
|
||||||
internal ReplaceTodoCommand To(string id) => new(
|
internal ReplaceTodoCommand To(string id) => new(
|
||||||
id,
|
id,
|
||||||
Title,
|
Title,
|
||||||
Project,
|
Project,
|
||||||
Description,
|
Description,
|
||||||
Status);
|
Status,
|
||||||
|
Created);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,20 +14,14 @@ namespace Todo.Api.Hubs;
|
|||||||
public class TodoHub : Hub
|
public class TodoHub : Hub
|
||||||
{
|
{
|
||||||
private readonly ICurrentUserService _currentUserService;
|
private readonly ICurrentUserService _currentUserService;
|
||||||
private readonly IMediator _mediator;
|
|
||||||
private readonly ITodoRepository _todoRepository;
|
|
||||||
private readonly IUserConnectionStore _userConnectionStore;
|
private readonly IUserConnectionStore _userConnectionStore;
|
||||||
|
|
||||||
public TodoHub(
|
public TodoHub(
|
||||||
ITodoRepository todoRepository,
|
|
||||||
IUserConnectionStore userConnectionStore,
|
IUserConnectionStore userConnectionStore,
|
||||||
ICurrentUserService currentUserService,
|
ICurrentUserService currentUserService)
|
||||||
IMediator mediator)
|
|
||||||
{
|
{
|
||||||
_todoRepository = todoRepository;
|
|
||||||
_userConnectionStore = userConnectionStore;
|
_userConnectionStore = userConnectionStore;
|
||||||
_currentUserService = currentUserService;
|
_currentUserService = currentUserService;
|
||||||
_mediator = mediator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task OnConnectedAsync()
|
public override Task OnConnectedAsync()
|
||||||
@ -46,149 +40,4 @@ public class TodoHub : Hub
|
|||||||
|
|
||||||
return base.OnDisconnectedAsync(exception);
|
return base.OnDisconnectedAsync(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateTodo(
|
|
||||||
string todoTitle,
|
|
||||||
string? projectName,
|
|
||||||
string? description)
|
|
||||||
{
|
|
||||||
if (todoTitle is null)
|
|
||||||
throw new ArgumentException("title cannot be null");
|
|
||||||
|
|
||||||
//var userId = GetUserId();
|
|
||||||
|
|
||||||
//var _ = await _todoRepository.CreateTodoAsync(todoTitle, projectName, userId);
|
|
||||||
await _mediator.Send(
|
|
||||||
new CreateTodoCommand(
|
|
||||||
todoTitle,
|
|
||||||
projectName,
|
|
||||||
description));
|
|
||||||
|
|
||||||
await GetInboxTodos();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task UpdateTodo(string todoId, bool todoStatus)
|
|
||||||
{
|
|
||||||
var userId = GetUserId();
|
|
||||||
await _todoRepository.UpdateTodoStatus(
|
|
||||||
todoId,
|
|
||||||
todoStatus,
|
|
||||||
userId);
|
|
||||||
|
|
||||||
await GetInboxTodos();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task GetTodos()
|
|
||||||
{
|
|
||||||
var todos = await _todoRepository.GetTodosAsync();
|
|
||||||
var serializedTodos = JsonSerializer.Serialize(
|
|
||||||
todos
|
|
||||||
.Select(
|
|
||||||
t => new TodoResponse
|
|
||||||
{
|
|
||||||
Id = t.Id,
|
|
||||||
Title = t.Title,
|
|
||||||
Status = t.Status,
|
|
||||||
Project = t.Project,
|
|
||||||
Description = t.Description
|
|
||||||
})
|
|
||||||
.ToList());
|
|
||||||
|
|
||||||
await RunOnUserConnections(
|
|
||||||
async connections =>
|
|
||||||
await Clients.Clients(connections)
|
|
||||||
.SendAsync("todos", serializedTodos));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task GetInboxTodos()
|
|
||||||
{
|
|
||||||
var todos = await _todoRepository.GetNotDoneTodos();
|
|
||||||
var serializedTodos = JsonSerializer.Serialize(
|
|
||||||
todos
|
|
||||||
.Select(
|
|
||||||
t => new TodoResponse
|
|
||||||
{
|
|
||||||
Id = t.Id,
|
|
||||||
Title = t.Title,
|
|
||||||
Status = t.Status,
|
|
||||||
Project = t.Project,
|
|
||||||
Description = t.Description
|
|
||||||
})
|
|
||||||
.ToList());
|
|
||||||
|
|
||||||
await RunOnUserConnections(
|
|
||||||
async connections =>
|
|
||||||
await Clients.Clients(connections)
|
|
||||||
.SendAsync("getInboxTodos", serializedTodos));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task GetTodo(string todoId)
|
|
||||||
{
|
|
||||||
var todo = await _todoRepository.GetTodoByIdAsync(todoId);
|
|
||||||
var serializedTodo = JsonSerializer.Serialize(
|
|
||||||
new TodoResponse
|
|
||||||
{
|
|
||||||
Id = todo.Id,
|
|
||||||
Project = todo.Project,
|
|
||||||
Status = todo.Status,
|
|
||||||
Title = todo.Title,
|
|
||||||
Description = todo.Description
|
|
||||||
});
|
|
||||||
|
|
||||||
await RunOnUserConnections(
|
|
||||||
async connections =>
|
|
||||||
await Clients.Clients(connections)
|
|
||||||
.SendAsync("getTodo", serializedTodo));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ReplaceTodo(string updateTodoRequest)
|
|
||||||
{
|
|
||||||
var updateTodo =
|
|
||||||
JsonSerializer.Deserialize<UpdateTodoRequest>(updateTodoRequest);
|
|
||||||
if (updateTodo is null)
|
|
||||||
throw new InvalidOperationException("Could not parse invalid updateTodo");
|
|
||||||
|
|
||||||
var userId = GetUserId();
|
|
||||||
|
|
||||||
var updatedTodo = await _todoRepository.UpdateTodoAsync(
|
|
||||||
new Core.Entities.Todo
|
|
||||||
{
|
|
||||||
Id = updateTodo.Id,
|
|
||||||
Project = updateTodo.Project,
|
|
||||||
Status = updateTodo.Status,
|
|
||||||
Title = updateTodo.Title,
|
|
||||||
AuthorId = userId,
|
|
||||||
Description = updateTodo.Description
|
|
||||||
});
|
|
||||||
|
|
||||||
var serializedTodo = JsonSerializer.Serialize(
|
|
||||||
new TodoResponse
|
|
||||||
{
|
|
||||||
Id = updatedTodo.Id,
|
|
||||||
Project = updatedTodo.Project,
|
|
||||||
Status = updatedTodo.Status,
|
|
||||||
Title = updatedTodo.Title,
|
|
||||||
Description = updatedTodo.Description
|
|
||||||
});
|
|
||||||
|
|
||||||
await RunOnUserConnections(
|
|
||||||
async connections =>
|
|
||||||
await Clients.Clients(connections)
|
|
||||||
.SendAsync("getTodo", serializedTodo));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RunOnUserConnections(
|
|
||||||
Func<IEnumerable<string>, Task> action)
|
|
||||||
{
|
|
||||||
var userId = GetUserId();
|
|
||||||
var connections =
|
|
||||||
await _userConnectionStore.GetConnectionsAsync(userId);
|
|
||||||
|
|
||||||
await action(connections);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetUserId() => _currentUserService.GetUserId() ??
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"User id was invalid. Something has gone terribly wrong");
|
|
||||||
}
|
}
|
@ -9,9 +9,10 @@ namespace Todo.Core.Application.Commands.Todo;
|
|||||||
public record ReplaceTodoCommand(
|
public record ReplaceTodoCommand(
|
||||||
string Id,
|
string Id,
|
||||||
string Title,
|
string Title,
|
||||||
string Project,
|
string? Project,
|
||||||
string Description,
|
string? Description,
|
||||||
bool Status) : IRequest<Unit>
|
bool Status,
|
||||||
|
long Created) : IRequest<Unit>
|
||||||
{
|
{
|
||||||
internal class Handler : IRequestHandler<ReplaceTodoCommand, Unit>
|
internal class Handler : IRequestHandler<ReplaceTodoCommand, Unit>
|
||||||
{
|
{
|
||||||
@ -43,13 +44,12 @@ public record ReplaceTodoCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Entities.Todo To(string authorId) => new()
|
private Entities.Todo To(string authorId) => new(
|
||||||
{
|
Id,
|
||||||
Id = Id,
|
Title,
|
||||||
Description = Description,
|
Status,
|
||||||
Project = Project,
|
Project,
|
||||||
Status = Status,
|
authorId,
|
||||||
Title = Title,
|
Description,
|
||||||
AuthorId = authorId
|
Created);
|
||||||
};
|
|
||||||
}
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using MediatR;
|
||||||
|
using Todo.Core.Interfaces.Persistence;
|
||||||
|
using Todo.Core.Interfaces.User;
|
||||||
|
|
||||||
|
namespace Todo.Core.Application.Queries.Todos;
|
||||||
|
|
||||||
|
public record GetActiveTodosQuery : IRequest<IEnumerable<TodoViewModel>>
|
||||||
|
{
|
||||||
|
internal class Handler : IRequestHandler<GetActiveTodosQuery, IEnumerable<TodoViewModel>>
|
||||||
|
{
|
||||||
|
private readonly ICurrentUserService _currentUserService;
|
||||||
|
private readonly ITodoRepository _todoRepository;
|
||||||
|
|
||||||
|
public Handler(ICurrentUserService currentUserService, ITodoRepository todoRepository)
|
||||||
|
{
|
||||||
|
_currentUserService = currentUserService;
|
||||||
|
_todoRepository = todoRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<TodoViewModel>> Handle(
|
||||||
|
GetActiveTodosQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var userId = _currentUserService.GetUserId();
|
||||||
|
if (userId is null)
|
||||||
|
throw new InvalidOperationException("Cannot get userId");
|
||||||
|
|
||||||
|
var todos = await _todoRepository.GetActiveTodosAsync(userId);
|
||||||
|
|
||||||
|
return todos.Select(TodoViewModel.From);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using MediatR;
|
||||||
|
using Todo.Core.Interfaces.Persistence;
|
||||||
|
using Todo.Core.Interfaces.User;
|
||||||
|
|
||||||
|
namespace Todo.Core.Application.Queries.Todos;
|
||||||
|
|
||||||
|
public record GetTodosQuery : IRequest<IEnumerable<TodoViewModel>>
|
||||||
|
{
|
||||||
|
internal class Handler : IRequestHandler<GetTodosQuery, IEnumerable<TodoViewModel>>
|
||||||
|
{
|
||||||
|
private readonly ICurrentUserService _currentUserService;
|
||||||
|
private readonly ITodoRepository _todoRepository;
|
||||||
|
|
||||||
|
public Handler(ICurrentUserService currentUserService, ITodoRepository todoRepository)
|
||||||
|
{
|
||||||
|
_currentUserService = currentUserService;
|
||||||
|
_todoRepository = todoRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<TodoViewModel>> Handle(
|
||||||
|
GetTodosQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var userId = _currentUserService.GetUserId();
|
||||||
|
if (userId is null)
|
||||||
|
throw new InvalidOperationException("Cannot get userId");
|
||||||
|
|
||||||
|
var todos = await _todoRepository.GetTodosAsync(userId);
|
||||||
|
|
||||||
|
return todos.Select(TodoViewModel.From);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,10 @@
|
|||||||
namespace Todo.Core.Entities;
|
namespace Todo.Core.Entities;
|
||||||
|
|
||||||
public record Todo
|
public record Todo(
|
||||||
{
|
string Id,
|
||||||
public string Id { get; init; }
|
string Title,
|
||||||
public string Title { get; init; }
|
bool Status,
|
||||||
public bool Status { get; init; }
|
string? Project,
|
||||||
public string? Project { get; init; }
|
string AuthorId,
|
||||||
public string AuthorId { get; init; }
|
string? Description,
|
||||||
public string? Description { get; init; }
|
long Created);
|
||||||
public long Created { get; init; }
|
|
||||||
}
|
|
@ -1,3 +1,5 @@
|
|||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Todo.Core.Interfaces.Persistence;
|
namespace Todo.Core.Interfaces.Persistence;
|
||||||
|
|
||||||
public interface ITodoRepository
|
public interface ITodoRepository
|
||||||
@ -9,14 +11,14 @@ public interface ITodoRepository
|
|||||||
string userId,
|
string userId,
|
||||||
long created);
|
long created);
|
||||||
|
|
||||||
Task<IEnumerable<Entities.Todo>> GetTodosAsync();
|
Task<IEnumerable<Entities.Todo>> GetTodosAsync(string userId, CancellationToken cancellationToken = new());
|
||||||
|
|
||||||
Task UpdateTodoStatus(
|
Task UpdateTodoStatus(
|
||||||
string todoId,
|
string todoId,
|
||||||
bool todoStatus,
|
bool todoStatus,
|
||||||
string userId);
|
string userId);
|
||||||
|
|
||||||
Task<IEnumerable<Entities.Todo>> GetNotDoneTodos();
|
|
||||||
Task<Entities.Todo> GetTodoByIdAsync(string todoId);
|
Task<Entities.Todo> GetTodoByIdAsync(string todoId);
|
||||||
Task<Entities.Todo> UpdateTodoAsync(Entities.Todo todo);
|
Task<Entities.Todo> UpdateTodoAsync(Entities.Todo todo);
|
||||||
|
Task<IEnumerable<Entities.Todo>> GetActiveTodosAsync(string authorId, CancellationToken cancellationToken = new());
|
||||||
}
|
}
|
@ -20,4 +20,13 @@ public record MongoTodo
|
|||||||
public string? ProjectName { get; set; } = string.Empty;
|
public string? ProjectName { get; set; } = string.Empty;
|
||||||
public string AuthorId { get; set; }
|
public string AuthorId { get; set; }
|
||||||
public long Created { get; set; } = 0;
|
public long Created { get; set; } = 0;
|
||||||
|
|
||||||
|
public Core.Entities.Todo To() => new(
|
||||||
|
Id,
|
||||||
|
Title,
|
||||||
|
Status,
|
||||||
|
ProjectName,
|
||||||
|
AuthorId,
|
||||||
|
Description,
|
||||||
|
Created);
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
using MongoDB.Driver;
|
using MongoDB.Driver;
|
||||||
using Todo.Core.Interfaces.Persistence;
|
using Todo.Core.Interfaces.Persistence;
|
||||||
using Todo.Persistence.Mongo.Repositories.Dtos;
|
using Todo.Persistence.Mongo.Repositories.Dtos;
|
||||||
@ -27,40 +28,21 @@ public class TodoRepository : ITodoRepository
|
|||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
ProjectName = projectName,
|
ProjectName = projectName,
|
||||||
|
Status = false,
|
||||||
AuthorId = userId,
|
AuthorId = userId,
|
||||||
Description = description,
|
Description = description,
|
||||||
Created = created
|
Created = created
|
||||||
};
|
};
|
||||||
await _todosCollection.InsertOneAsync(todo);
|
await _todosCollection.InsertOneAsync(todo);
|
||||||
return new Core.Entities.Todo
|
return todo.To();
|
||||||
{
|
|
||||||
Id = todo.Id,
|
|
||||||
Title = todo.Title,
|
|
||||||
Status = false,
|
|
||||||
Project = todo.ProjectName,
|
|
||||||
Description = todo.Description,
|
|
||||||
AuthorId = todo.AuthorId,
|
|
||||||
Created = todo.Created
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Core.Entities.Todo>> GetTodosAsync()
|
public async Task<IEnumerable<Core.Entities.Todo>> GetTodosAsync(string userId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var todos = await _todosCollection.FindAsync(_ => true);
|
var todos = await _todosCollection.FindAsync(t => t.AuthorId == userId, cancellationToken: cancellationToken);
|
||||||
return todos
|
return todos
|
||||||
.ToEnumerable()
|
.ToEnumerable()
|
||||||
.Select(
|
.Select(t => t.To());
|
||||||
t =>
|
|
||||||
new Core.Entities.Todo
|
|
||||||
{
|
|
||||||
Id = t.Id,
|
|
||||||
Title = t.Title,
|
|
||||||
Status = t.Status,
|
|
||||||
Project = t.ProjectName,
|
|
||||||
Description = t.Description,
|
|
||||||
AuthorId = t.AuthorId,
|
|
||||||
Created = t.Created
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateTodoStatus(
|
public async Task UpdateTodoStatus(
|
||||||
@ -74,27 +56,12 @@ public class TodoRepository : ITodoRepository
|
|||||||
Builders<MongoTodo>.Update.Set(t => t.Status, todoStatus));
|
Builders<MongoTodo>.Update.Set(t => t.Status, todoStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Core.Entities.Todo>> GetNotDoneTodos()
|
|
||||||
{
|
|
||||||
var todos = await GetTodosAsync();
|
|
||||||
return todos.Where(t => t.Status == false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Core.Entities.Todo> GetTodoByIdAsync(string todoId)
|
public async Task<Core.Entities.Todo> GetTodoByIdAsync(string todoId)
|
||||||
{
|
{
|
||||||
var todoCursor = await _todosCollection.FindAsync(f => f.Id == todoId);
|
var todoCursor = await _todosCollection.FindAsync(f => f.Id == todoId);
|
||||||
var todo = await todoCursor.FirstOrDefaultAsync();
|
var todo = await todoCursor.FirstOrDefaultAsync();
|
||||||
|
|
||||||
return new Core.Entities.Todo
|
return todo.To();
|
||||||
{
|
|
||||||
Id = todo.Id,
|
|
||||||
Project = todo.ProjectName,
|
|
||||||
Status = todo.Status,
|
|
||||||
Title = todo.Title,
|
|
||||||
Description = todo.Description,
|
|
||||||
AuthorId = todo.AuthorId,
|
|
||||||
Created = todo.Created
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Core.Entities.Todo> UpdateTodoAsync(
|
public async Task<Core.Entities.Todo> UpdateTodoAsync(
|
||||||
@ -113,15 +80,18 @@ public class TodoRepository : ITodoRepository
|
|||||||
Created = todo.Created
|
Created = todo.Created
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Core.Entities.Todo
|
return updatedTodo.To();
|
||||||
{
|
}
|
||||||
Id = updatedTodo.Id,
|
|
||||||
Project = updatedTodo.ProjectName,
|
public async Task<IEnumerable<Core.Entities.Todo>> GetActiveTodosAsync(
|
||||||
Status = updatedTodo.Status,
|
string authorId,
|
||||||
Title = updatedTodo.Title,
|
CancellationToken cancellationToken)
|
||||||
AuthorId = updatedTodo.AuthorId,
|
{
|
||||||
Description = updatedTodo.Description,
|
var todosCursor = await _todosCollection.FindAsync(
|
||||||
Created = updatedTodo.Created
|
t => t.AuthorId == authorId && t.Status == false,
|
||||||
};
|
cancellationToken: cancellationToken);
|
||||||
|
var todos = todosCursor.ToEnumerable(cancellationToken);
|
||||||
|
|
||||||
|
return todos.Select(t => t.To());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,6 +23,22 @@ export const todosApi = createApi({
|
|||||||
: [],
|
: [],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
getActiveTodos: build.query<Todo[], void>({
|
||||||
|
query: () => ({
|
||||||
|
url: `api/todos`,
|
||||||
|
params: {
|
||||||
|
onlyActive: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
providesTags: (result) =>
|
||||||
|
result
|
||||||
|
? [
|
||||||
|
...result.map(({ id }) => ({ type: "todo" as const, id })),
|
||||||
|
{ type: "todo", id: "LIST" },
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
}),
|
||||||
|
|
||||||
createTodo: build.mutation<string, Partial<Todo>>({
|
createTodo: build.mutation<string, Partial<Todo>>({
|
||||||
query(body) {
|
query(body) {
|
||||||
return {
|
return {
|
||||||
|
@ -6,7 +6,7 @@ import { todosApi } from "@src/infrastructure/apis/todosApi";
|
|||||||
import { AddTodo } from "@src/components/todos/addTodo";
|
import { AddTodo } from "@src/components/todos/addTodo";
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const { data, isLoading } = todosApi.useGetAllTodosQuery();
|
const { data, isLoading } = todosApi.useGetActiveTodosQuery();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return "loading...";
|
return "loading...";
|
||||||
|
Loading…
Reference in New Issue
Block a user