# Github issues viewer

### Introduction

We used this example to experiment with defining different isolated states and show how they can be used together when different asynchronous calls are needed. In this section we have only shown the code in Typescript.

### Creating the Conan states

We have divided the data handled by the app into four independent states:

The remote repository connection details is called *repoState$*:

```typescript
export const repoState$: RepoState = 
        Conan.light<RepoData>('repo', {org: "rails", repo: "rails", page: 1})
```

The specific repository data information retrieved is modelled in *repoDetailsState$ :*

```typescript
export interface RepoDetailsData {
    openIssuesCount: number
    error: string | null
}

export const repoDetailsState$: RepoDetailsState = Conan.state<RepoDetailsData>({
    name: 'repo-details',
    initialData: {openIssuesCount: -1, error: null},
    reducers: repoDetailsReducersFn,
    actions: repoDetailsActionsFn
});
```

The issues data is modelled in *issuesState$*:

```typescript
export type IssuesState = ConanState<IssuesData, IssuesActions>;

export interface IssuesData {
    issuesByNumber: Record<number, Issue>;
    issues: Issue[];
    issueId?: number;
    displayType: 'issues' | 'comments';
}

export const issuesState$: IssuesState = Conan.state<IssuesData, IssuesReducers, IssuesActions>({
    name: 'issues',
    initialData: {
        issuesByNumber: {} as Record<number, Issue>,
        issues: [],
        displayType: "issues"
    },
    reducers: issuesReducersFn,
    actions: issueActionsFn
})
```

�and finally the issues comments is modelled in *issuesCommentsState$*:

```typescript
export interface IssuesCommentsData {
    commentsByIssue: Record<number, IssueComment[] | undefined>
}

export type IssuesCommentsState = ConanState<IssuesCommentsData, IssuesCommentsActions>;

export const issuesCommentsState$: IssuesCommentsState = Conan.state<IssuesCommentsData, IssuesCommentsReducersFn, IssuesCommentsActions>({
    name: 'issues-comments',
    initialData: {
        commentsByIssue: {} as Record<number, IssueComment[]>,
    },
    reducers: issuesCommentsReducers,
    actions: issueCommentsActionsFn
})
```

### �Adding async operations

In this app we need to fetch the issues of a given repository, fetch the information of that same repository and finally retrieve the comments of a given issue. We have implemented this logic in a service called *IssuesServiceImpl* which uses [ConanJs Asap](broken://pages/-M9sjuF--Cq3HxHBZoZM#asap) to implement the asynchronous calls:

```typescript
export interface IssuesService {
    fetch(repo: string, org: string, page: number): Asap<Issue[]>;

    fetchComments(commentsUrl: string): Asap<IssueComment[]>;

    fetchRepoDetails(org: string, repo: string): Asap<RepoDetails>;
}

export class IssuesServiceImpl implements IssuesService {
    fetch(repo: string, org: string, page: number = 1): Asap<Issue[]> {
        return Asaps.fetch<Issue[]>(`https://api.github.com/repos/${org}/${repo}/issues?per_page=25&page=${page}`);
    }

    fetchComments(commentsUrl: string): Asap<IssueComment[]> {
        return Asaps.fetch<IssueComment[]>(commentsUrl);
    }

    fetchRepoDetails(org: string, repo: string): Asap<RepoDetails> {
        return Asaps.fetch(`https://api.github.com/repos/${org}/${repo}`);
    }
}
```

### �Adding DI dependencies

We have made these states and the service available to all the app via [ConanJs DI](/dependency-injection.md):

```typescript
interface AuxDependencies {
    issuesService: IssuesService
}

export let diContext = DiContextFactory.createContext<App, AuxDependencies>(
    {
        issuesCommentsState: issuesCommentsState$,
        issuesState: issuesState$,
        repoState: repoState$,
        repoDetailsState: repoDetailsState$
    }, {
        issuesService: IssuesServiceImpl
    }
);

export interface App {
    issuesState: IssuesState;
    issuesCommentsState: IssuesCommentsState;
    repoState: RepoState;
    repoDetailsState: RepoDetailsState;
}
```

### �Fetching the issues

We need to implement an async call to retrieve the issues of the repository given. We can see how that is done in the actions passed to ***issuesState$**:*

```typescript
export interface IssuesActions {
    fetch(repo: string, org: string, page: number): Asap<IssuesData>;

    fetchIssue(issueId: number): IssuesData;

    showIssues(): IssuesData;
}


export const issueActionsFn: ActionsFn<IssuesData, IssuesReducers, IssuesActions> = thread => ({
    fetch(repo, org, page): Asap<IssuesData> {
        return thread.monitor(
            diContext.issuesService.fetch(repo, org, page).catch(() => thread.reducers.$fetch([])),
            (issues, reducers) => reducers.$fetch(issues as Issue[]),
            'fetch',
            [repo, org, page]
        )
    },
    fetchIssue(issueId: number): IssuesData {
        return thread.reducers.$fetchIssue(issueId);
    },
    showIssues(): IssuesData {
        return thread.reducers.$switchDisplay("issues");
    }
})
```

{% hint style="info" %}
The fist action fetch has the async call, so it's wrapped with thread.monitor. The other 2 actions are synchronous, so they can just call the reducer
{% endhint %}

{% hint style="success" %}
we can access the *issuesService* from anywhere, since it's defined in ConanJs DI
{% endhint %}

### Fetching the repository information

We need another async call to retrieve the repository information, but this time bound to the ***repoDetailsState$**.* This state just has an action passed in, and it will look like:

```typescript
export interface RepoDetailsActions {
    fetchRepoDetails(repo, org): Asap<RepoDetailsData>;
}

export const repoDetailsActionsFn: ActionsFn<RepoDetailsData, RepoDetailsReducers, RepoDetailsActions> = thread => ({
    fetchRepoDetails(repo, org): Asap<RepoDetailsData> {
        return thread.monitor(
            diContext.issuesService.fetchRepoDetails(repo, org).catch(() => thread.reducers.$fetchRepoDetails(-1, "error loading")),
            (repoDetails, reducers) => reducers.$fetchRepoDetails(repoDetails.open_issues_count, ""),
            'fetchRepoDetails',
            [repo, org]
        )
    }
});
```

### Fetching comments

Our last async call will happen when the user selects to open an issue, and we need to show the comments. So the ***issuesCommentsState$***  will have an action that looks like:

```typescript
export interface IssuesCommentsActions {
    fetchComments(issue: Issue): Asap<IssuesCommentsData>;
}

export const issueCommentsActionsFn: ActionsFn<IssuesCommentsData, IssuesCommentsReducersFn, IssuesCommentsActions> = thread => ({
    fetchComments(issue: Issue): Asap<IssuesCommentsData> {
        return thread.monitor(
            diContext.issuesService.fetchComments(issue.comments_url).catch(() => thread.reducers.$fetch([])),
            (comments, reducers) => reducers.$fetchComments(issue.id, comments as IssueComment[]),
            'fetchComments',
            issue.comments_url
        )
    }
})
```

### �Displaying the issues pages

Let's now see how we can display the issues and the repository information. The following fragment belongs to the functional component *IssuesListPage*:

```typescript
<div id="issue-list-page">
    {repoDetailsState$.connectMap<HeaderProps>(
        IssuesPageHeader,
        data => ({
            org: org,
            repo: repo,
            openIssuesCount: data.openIssuesCount
        })
    )
    }
    {diContext.issuesState.connect(IssuesList)}
</div>
```

�It uses connectMap to link **repoDetailsState$** with the component *IssuesPageHeader*, and the DI context to fully connect issuesState with the component *IssuesList*.

### Displaying the comments

The component IssueDetailsPage uses a ConanJs hook [*useConanState*](/data/conan-state/subscribing-state/connecting.md#hooks) to connect with the state ***issuesCommentsState$*** and retrieve the issues in its own useEffect:

```typescript
const [commentsState] = useConanState<IssuesCommentsData, IssuesCommentsActions>(issuesCommentsState$);

useEffect(() => {
    if (issue) {
        fetchComments(issue)
    }
}, []);

const comments = commentsState.commentsByIssue[issue.id];
let renderedComments;
if (comments) {
    renderedComments = <IssueComments issue={issue} comments={comments}/>
}
```

�

### Getting the code

The full code for this example is available at

{% embed url="<https://github.com/conan-js/conan-js-examples/tree/master/issues-viewer>" %}

or the code sandbox below, for more details on this ConanJs feature.

### Code sandbox

{% embed url="<https://codesandbox.io/s/q9ytd>" %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.conanjs.io/tutorials/conan-state-demos/github-issues-viewer.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
