Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cnb 1308 #591

Merged
merged 5 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ title: Adding Some State
description: Tutorial > Adding Some State
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

In [previous section](/tutorial/static-view) of the tutorial, we prepared basic markup and custom styling
thanks to the [Bootstrap CSS library](https://getbootstrap.com/).
In this section, we're going to add some functionality to our application.
Expand All @@ -20,6 +23,9 @@ You can notice that by default, our bootstrapped application includes some pre-d
methods along with very long JSDoc comments. Feel free to read through these comments
but to make this tutorial simpler, we're going to replace contents of this file with following code:

<Tabs>
<TabItem value="JavaScript">

```javascript
import { AbstractController } from '@ima/core';

Expand All @@ -42,6 +48,57 @@ export class HomeController extends AbstractController {
}
```

</TabItem>
<TabItem value="TypeScript">

```typescript
import {
AbstractController,
Dependencies,
Dictionary,
LoadedResources,
MetaManager,
Router,
Settings,
} from '@ima/core';

export type PostData = {
content: string;
author: string;
};
export type HomeControllerState = {
posts: PostData[];
};

export class HomeController extends AbstractController<HomeControllerState> {
static $dependencies: Dependencies = [];

constructor() {
super();
}

load(): HomeControllerState<HomeControllerState> {
return {};
}

setMetaParams(
loadedResources: LoadedResources,
metaManager: MetaManager,
router: Router,
dictionary: Dictionary,
settings: Settings
): void {
metaManager.setTitle('Guestbook');
}
}
```

> **TypeScript:** As you can see, the TypeScript code is a lot more complex.
The main reason is adding types `PostData` and `HomeControllerState` that we will use later in this course.

</TabItem>
</Tabs>

The `AbstractController` class defines some methods which are executed
in different parts of it's lifecycle, you can [read more about this in the documentation](../basic-features/controller-lifecycle.md)
One of the main methods you're going to use frequently is the `load()` method.
Expand Down Expand Up @@ -140,6 +197,9 @@ server and move the structure to an external JSON file.
Let's return to our view in the `app/page/home/HomeView.jsx` file. Replace the
`render()` method with the following code snippet:

<Tabs>
<TabItem value="JavaScript">

```jsx
render() {
return (
Expand Down Expand Up @@ -198,6 +258,81 @@ _renderPosts() {
}
```

</TabItem>
<TabItem value="TypeScript">

```tsx
import './homeView.less';
import { PostData } from 'app/page/home/HomeController';
import { usePageContext } from '@ima/react-page-renderer';

type HomeViewProps = {
posts: PostData[];
};

export function HomeView({ posts }: HomeViewProps) {
const _pageContext = usePageContext();

const _renderPosts = () => {
return posts.map((post: PostData, index) => (
<div className='post card card-default' key={index}>
<div className='card-body'>{post.content}</div>
<div className='post-author card-footer'>{post.author}</div>
</div>
));
};

return (
<div className='l-home container'>
<h1>Guestbook</h1>

<div className='posting-form card'>
<form action='' method='post'>
<h5 className='card-header'>Add a post</h5>
<div className='card-body'>
<div className='form-group'>
<label htmlFor='postForm-name'>Name:</label>
<input
id='postForm-name'
className='form-control'
type='text'
name='author'
placeholder='Your name'
/>
</div>
<div className='form-group'>
<label htmlFor='postForm-content'>Post:</label>
<textarea
id='postForm-content'
className='form-control'
name='content'
placeholder='What would you like to tell us?'
/>
</div>
</div>
<div className='card-footer'>
<button type='submit' className='btn btn btn-outline-primary'>
Submit
<div className='ripple-wrapper' />
</button>
</div>
</form>
</div>
<hr />
<div className='posts'>
<h2>Posts</h2>
{_renderPosts()}
</div>
</div>
);
}
```

> **TypeScript: ** New type `HomeViewProps` has been used to correctly accept typed props in our `HomeView`.

</TabItem>
</Tabs>

We have replaced the old sequence of
`<div className='post card card-default'>` tags with the
`{this._renderPosts()}` expression, which tells React to insert the return
Expand Down Expand Up @@ -240,6 +375,9 @@ and `app/component/post/post.less` files.

Put the following code into the `Post.jsx` file:

<Tabs>
<TabItem value="JavaScript">

```jsx
import { AbstractComponent } from '@ima/react-page-renderer';
import React from 'react';
Expand All @@ -260,26 +398,82 @@ export default class Post extends AbstractComponent {
}
```

</TabItem>
<TabItem value="TypeScript">

```tsx
import './post.less';

type PostProps = {
content: string;
author: string;
};

export function Post({ content, author }: PostProps) {
return (
<div className='post card card-default'>
<div className='card-body'>{content}</div>
<div className='post-author card-footer'>{author}</div>
</div>
);
}
```

> **TypeScript: ** With `PostProps` type, in TypeScript we can directly destructure the props in the function
parameters and use their single attributes in the return function.

</TabItem>
</Tabs>


In this component we access the post content and author name in our `render()`
method using the `this.props` object, which contains a hash object of
properties passed to the React component by whatever code is using it.

To use our new component, we need to update the `_renderPosts()` method in the
`app/page/home/HomeView.jsx` file to the following code:

<Tabs>
<TabItem value="JavaScript">

```jsx
return posts.map((post, index) => {
return <Post key={index} content={post.content} author={post.author} />;
});
```

</TabItem>
<TabItem value="TypeScript">

```tsx
return posts.map((post: PostData, index) => (
<Post key={index} content={post.content} author={post.author} />
));
```

</TabItem>
</Tabs>

...and import the `Post` component by adding the following import to the
beginning of the file:

<Tabs>
<TabItem value="JavaScript">

```javascript
import Post from 'app/component/post/Post';
```

</TabItem>
<TabItem value="TypeScript">

```typescript
import { Post } from 'app/component/post/Post';
```

</TabItem>
</Tabs>

> **Note**: You can notice that so far we **haven't used relative imports** when importing
our custom JS modules from inside of the app directory structure. This is
because IMA.js adds the `app` directory to the **lookup path**. This means that
Expand All @@ -303,6 +497,9 @@ We can further improve our page view structure by refactoring-out the
directory and the `app/component/postingForm/PostingForm.jsx` file. Then, put the
following code into the `app/component/postingForm/PostingForm.jsx` file:

<Tabs>
<TabItem value="JavaScript">

```jsx
import { AbstractComponent } from '@ima/react-page-renderer';
import React from 'react';
Expand Down Expand Up @@ -347,11 +544,59 @@ export default class PostingForm extends AbstractComponent {
}
```

</TabItem>
<TabItem value="TypeScript">

```tsx
export function PostingForm() {
return (
<div className='posting-form card'>
<form action='' method='post'>
<h5 className='card-header'>Add a post</h5>
<div className='card-body'>
<div className='form-group'>
<label htmlFor='postForm-name'>Name:</label>
<input
id='postForm-name'
className='form-control'
type='text'
name='author'
placeholder='Your name'
/>
</div>
<div className='form-group'>
<label htmlFor='postForm-content'>Post:</label>
<textarea
id='postForm-content'
className='form-control'
name='content'
placeholder='What would you like to tell us?'
/>
</div>
</div>
<div className='card-footer'>
<button type='submit' className='btn btn btn-outline-primary'>
Submit
<div className='ripple-wrapper' />
</button>
</div>
</form>
</div>
);
}
```

</TabItem>
</Tabs>

Nothing new here, we just extracted the code from home controller's view and
put it into a new React component.

Now update the `render()` method in the home controller's view:

<Tabs>
<TabItem value="JavaScript">

```jsx
return (
<div className="l-home container">
Expand All @@ -368,12 +613,48 @@ return (
);
```

</TabItem>
<TabItem value="TypeScript">

```tsx
return (
<div className='l-home container'>
<h1>Guestbook</h1>
<PostingForm />

<hr />

<div className='posts'>
<h2>Posts</h2>
{_renderPosts()}
</div>
</div>
);
```

</TabItem>
</Tabs>

To finish up, import the posting form component:

<Tabs>
<TabItem value="JavaScript">

```javascript
import PostingForm from 'app/component/postingForm/PostingForm';
```

</TabItem>
<TabItem value="TypeScript">

```tsx
import { PostingForm } from 'app/component/postingForm/PostingForm';
```

</TabItem>
</Tabs>


So far we've been **only refactoring our code and moving few bits around** to make it cleaner.
When you refresh the page, you should see the same page as you ended up with after the end of
the previous tutorial.
Expand Down
Loading
Loading