Structuring React application for scale (Part II)
Tags: react, redux, typescript, tutorial
Published: 2020-10-11 17:39:28

In the last tutorial, we defined the basic structure for a scaleable react application. To demonstrate how it all comes together, we are going to build a Reddit client where a user can search about multiple topics and get results in form of a list.

In case if you did not already, please refer to [Part I](https://dev.to/anishkargaonkar/structuring-react-application-for-scale-part-i-8bm) to understand the structure in depth.

![srp-optimize](https://dev-to-uploads.s3.amazonaws.com/i/uj7toqsjqy9hc21hquo7.gif) 

Github: [https://github.com/anishkargaonkar/react-reddit-client](https://github.com/anishkargaonkar/react-reddit-client)
Hosted On: [https://reddit-client-88d34d.netlify.app/](https://reddit-client-88d34d.netlify.app/)

# The Reddit Client

Let's start by creating a container called **Search** at `/src/cotainers/Search/Search.tsx`

```tsx
// /src/cotainers/Search/Search.tsx
import React, { FC } from "react";

type Props = {};

const Search: FC<Props> = (props: Props) => {
    return (
        <div>Search Container</div>
    )
};

export { Search };
```

and add it to the **Navigator** component at `/src/navigator/Navigator.tsx`

```tsx
// /src/navigator/Navigator.tsx
import React, { FC } from "react";
import { Switch, Route, BrowserRouter as Router } from "react-router-dom";
import { Search } from "../containers/Search/Search";

type Props = {};

const Navigator: FC<Props> = () => {
  return (
    <Router>
      <Switch>
        <Route path="/" component={Search} />
      </Switch>
    </Router>
  );
};

export { Navigator };
```

After doing the above changes, the folder structure should look something like this

![Untitled](https://dev-to-uploads.s3.amazonaws.com/i/aoopjzpsshlhujlnf938.png)
 
## Adding search state

We'll be using Reddit's search API to query and fetch results. The format is given below

```tsx
https://www.reddit.com/r/all/search.json?q=<query>&limit=<limit>
```

You can find more details on Reddit's [official documentation](https://www.reddit.com/dev/api/)

Let's define our API endpoints in `.env` 

```tsx
// /.env

REACT_APP_PRODUCTION_API_ENDPOINT = "https://www.reddit.com"
REACT_APP_DEVELOPMENT_API_ENDPOINT = "https://www.reddit.com"
```

In our case, both endpoints are going to be the same as we do not have separate environments for our app's back-end. 

Before defining our redux state first we need to know how would our data looks, so let's first define the model by creating a file `types.ts` in our **Search** container.

Generally, these models are decided early on before starting the project which off-course evolves over a period of time. Sometimes it might happen that we don't have a model beforehand and in that case, the developer is free to use his/her imagination based on the use case. But it's better to start after having a starting point which helps to avoid a lot of changes in later stages. For our use case, we can make a query to the above search query link to get the response and use a typescript generator tool like [json2ts](http://json2ts.com/) to get our typescript schema.

Note: If you are using JavaScript, you can skip this part but do take a look at the model once.

```tsx
// src/containers/Search/types.ts
export interface Result {
  title: string;
  thumbnail: string;
  permalink: string;
}

export interface SearchResults {
  after: string;
  dist: number;
  modhash: string;
    children: {
        kind: string;
        data: Result;
  };
  before?: any;
}

// reddit API response Model
export interface Search {
  kind: string;
  data: SearchResults;
}
```

We have defined a model called **Search** which represents the data sent from the Reddit search API. To keep it simple we've omitted attributes that are not used in the app. **Result** model represents each Reddit result.

We'll also add a **SearchQuery** interface in `types.ts` where we will define query parameters required to make a Reddit search

```tsx
// src/containers/Search/types.ts

... // Search Result model

export interface SearchQuery {
  query: string;
  limit: number;
};
```

Now let's define the redux state and actions types for **Search** container in `types.ts` 

```tsx
// src/containers/Search/types.ts
import { CustomError } from "../../utils/api-helper";

... // Search Result interface 

... // Search Query interface

// Search action types
export enum SearchActionTypes {
    GET_RESULTS_REQUEST = "@@search/GET_RESULTS_REQUEST",
    GET_RESULTS_SUCCESS = "@@search/GET_RESULTS_SUCCESS",
    GET_RESULTS_ERROR = "@@search/GET_RESULTS_ERROR",  
}

interface Errors {
  results: CustomError | null
}

// Search redux state 
export interface SearchState {
   isLoading: boolean,
   results: Search | null,
   errors: Errors
}
```

For search API requests there can only be 3 states at any given point of time .i.e.

- **GET_RESULTS_REQUEST:** while fetching results
- **GET_RESULTS_SUCCESS:** when we receive a successful response
- **GET_RESULTS_ERROR:** when we receive an error response

Similarly, for the **Search** container state we've defined

- **isLoading:** boolean to keep a track if any API request is being made or not
- **results:** where search results are going to be stored.
- **errors:** where at most 1 error response for each attribute will be tracked (here we are tracking for `results`).

If you would have noticed we are using a pipe( | ) operator with `null` type which means that at any given point it's value will be either of type **T** or **null**. We can also use `undefined` but this way we'll need to always declare that attribute and assign a null value which in turn makes our code more readable.

Let's also add **SearchState** to the **ApplicationState** defined in `src/store.ts` and call it `search`

```tsx
// src/store.ts
... // imports
import { SearchState } from './containers/Search/reducer';

export type ApplicationState = {
  search: SearchState
};

function configureAppStore(initialState: ApplicationState) {
  ... // store configuration 
}

export { configureAppStore };
```

Let's define actions for Search state in redux. For this, we are going to use redux-toolkit's `createAction` and `createReducer` helper functions for actions and reducer respectively. 

```tsx
// src/containers/Search/action.ts
import { createAction } from "@reduxjs/toolkit";
import { CustomError } from "../../utils/api-helper";
import { Search, SearchActionTypes, SearchQuery } from "./types";

export const getResultsRequest = createAction<SearchQuery>(
  SearchActionTypes.GET_RESULTS_REQUEST
);

export const getResultsSuccess = createAction<Search>(
  SearchActionTypes.GET_RESULTS_SUCCESS
);

export const getResultsError = createAction<CustomError>(
  SearchActionTypes.GET_RESULTS_ERROR
);
```

Here we have defined 3 action types. Since we are using Typescript, we have also defined the payload type for `getResultsRequest` `getResultsSuccess` and `getResultsError`. The payload type will help connect the flow and avoid errors.

It's time to setup reducer for the **Search** state which will listen to dispatched action and if the action type matches, the redux state will be updated. To create the reducer, we are going to use the `createReducer` helper utility from redux-toolkit using builder callback notation which is recommended with Typescript. For more information feel free to check the [redux-toolkit docs.](https://redux-toolkit.js.org/api/createReducer) 

```tsx
// src/containers/Search/reducer.ts
import { createReducer } from "@reduxjs/toolkit";
import {
  getResultsError,
  getResultsRequest,
  getResultsSuccess,
} from "./action";
import { SearchState } from "./types";

const initalState: SearchState = {
  isLoading: false,
  results: null,
  errors: {
    results: null,
  },
};

const reducer = createReducer(initalState, (builder) => {
  return builder
    .addCase(getResultsRequest, (state, action) => {
      state.isLoading = true;
      state.results = null;
      state.errors.results = null;
    })
    .addCase(getResultsSuccess, (state, action) => {
      state.isLoading = false;
      state.results = action.payload;
    })
    .addCase(getResultsError, (state, action) => {
      state.isLoading = false;
      state.errors.results = action.payload;
    });
});

export { initalState as searchInitialState, reducer as searchReducer };
```

Here we are creating a reducer that will listen for **SearchActionTypes** created earlier and update state accordingly. Now for the sake of keeping this example simple, we are not considering pagination and other advance list operations. We'll assume that search results will be only fetched once and we'll keep data for the latest request therefore, we are resetting state when a new `getResultsRequest` is made. We are also exporting the initial state (**searchInitialState**) which will also represent the search state when the application is bootstrapped. 

NOTE: You can also use `createSlice` method provided by redux-toolkit which will create both actions as well as a reducer for you. Action types can be provided inline. For more information, you can refer to [redux-toolkit docs](https://redux-toolkit.js.org/api/createSlice). 

Now let's add the initial search state to the initial application state in `src/App.tsx`

```tsx
// src/App.tsx
import React from 'react';
import { Provider } from 'react-redux';
import { ApplicationState, configureAppStore } from './store';
import { Navigator } from "./navigator/Navigator";
import { searchInitialState } from './containers/Search/reducer';

const initialState: ApplicationState = {
  search: searchInitialState;
};

const store = configureAppStore(initialState);

function App() {
  return (
    <Provider store={store}>
      <Navigator />
    </Provider>
  );
}

export default App; 
```

We also need to add the search reducer in the root reducer by adding it to `src/reducer.ts` 

```tsx
// src/reducer.ts
import { combineReducers } from "@reduxjs/toolkit";
import { searchReducer } from './containers/Search/reducer';

const reducers = {
  search: searchReducer
};

function createRootReducer() {
    const rootReducer = combineReducers({
      ...reducers
    });
  
    return rootReducer;
};
  
export { createRootReducer };
```

Now when you run the application, you should be able to see a `search` state available in the redux state.

![Untitled 1](https://dev-to-uploads.s3.amazonaws.com/i/bafplbb5mu7b2lnml6ql.png) 

The folder structure will look something like this

![Untitled 2](https://dev-to-uploads.s3.amazonaws.com/i/gzerrud7mm8b8dw8cw9r.png) 

Now that we are done with the redux setup, it's time to setup saga middleware for the **Search** container. Let's start by creating a file `saga.ts` in the **Search** container and define a `getSearchResults` function which will listen for `GET_SEARCH_RESULTS` action type. In order to understand how redux-saga work you can check out their [official docs](https://redux-saga.js.org/). 

```tsx
// src/containers/Search/saga.ts

import { all, fork, takeLatest } from "redux-saga/effects";
import { getResultsRequest } from "./action";

function* getSearchResults() {
    // get search results API request
}

function* watchFetchRequest() {
  yield takeLatest(getResultsRequest.type, getSearchResults);
}

export default function* searchSaga() {
  yield all([fork(watchFetchRequest)]);
}
```

We have defined a **searchSaga** which we'll import in `store.ts` so that it is registered. `getSearchResults` will contain the code responsible for making an API request and depending on the response it'll dispatch a success or error action. 

Before that, we'll need to first create a function for making API requests in `src/services/Api.ts`. As mentioned above, to get search results from Reddit we can use the following endpoint and we'll pass the `query` & `limit` from the component.

```tsx
https://www.reddit.com/r/all/search.json?q=<query>&limit=<limit>
```

We have already added the base URL (https://www.reddit.com) as `API_ENDPOINT` in the environment configuration.  

Let's define a function **fetchSearchResults** and we'll use the `get` helper function from `src/utils/api-helper.ts`.  

```tsx
// src/services/Api.ts
import config from "../config/app";
import * as API from "../utils/api-helper";
import { SearchQuery } from "../containers/Search/types";

const { isProd } = config;

const API_ENDPOINT = isProd 
	? config.production 
	: config.development;

export const fetchSearchResults = (params: SearchQuery) => {
  const { query, limit } = params;
  const url = `${API_ENDPOINT}/r/all/search.json?q=${query}&limit=${limit}`;

  return API.get(url);
};
```

Now we can use **fetchSearchResults**, let's complete our search saga and make a get search API call.

Specifying the action as an argument to a saga is a bit tricky, we have to use TypeScript's [Type Guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards). Interestingly, it's mentioned in the redux-toolkit's [documentation](https://redux-toolkit.js.org/api/createAction#actioncreatormatch) as well. In short, we have to use actionCreator.match method of the actionCreator to discriminate down the passed action to the desired type. Thus, after discrimination, we receive the desired static typing for the matched action's payload.

After playing around with the response, I ended up with the following `saga.ts`. 

```tsx
// src/containers/Search/saga.ts
import { Action } from '@reduxjs/toolkit';
import { all, call, fork, put, takeLatest } from "redux-saga/effects";
import { getResultsError, getResultsRequest, getResultsSuccess } from "./action";
import * as Api from "../../services/Api";
import { getCustomError } from '../../utils/api-helper';

function* getSearchResults(action: Action) {
  try {
    if (getResultsRequest.match(action)) {
      const res = yield call(Api.fetchSearchResults, action.payload);
      const data = res.data;
      if (res.status !== 200) {
        yield put(getResultsError(data.error));
      } else {
        yield put(getResultsSuccess(data));
      }
    }
  } catch (err) {
    yield put(getResultsError(getCustomError(err)))
  }
}

function* watchFetchRequest() {
  yield takeLatest(getResultsRequest.type, getSearchResults);
}

export default function* searchSaga() {
  yield all([fork(watchFetchRequest)]);
}
```

To register **searchSaga**, simply import it in root saga at `src/saga.ts`.

```tsx
// src/saga.ts
import { all, fork } from "redux-saga/effects";
import searchSaga from "./containers/Search/saga";

function* rootSaga() {
    yield all([
        fork(searchSaga)
    ]);
};
  
export { rootSaga };
```

This completes the data setup for the application. Now we can start with UI implementation. The folder structure will look something like this

![Untitled 3](https://dev-to-uploads.s3.amazonaws.com/i/xuvszqcyljs2wgmscvij.png) 

## Setting up the UI

We can divide the UI into 2 parts

- **SearchInput**: It'll have an input field which will take in search query from the user
- **Results:** Basically here we'll show results from the query

Let's create a folder called `views` at `src/containers/Search/views` where the above-listed components will go. The `view` folder (sometimes named as `screens`) inside the container will contain components that are specific to that container or accessing the global state (in our case redux state).

For sake of simplicity and since making components such as Input and Loader is outside the scope of this article, I'll be using a components library [ant design](http://ant.design). But in case you are wondering, components that might be used in multiple places stateless or otherwise will go inside the `src/components` folder. 

Though if you are using hooks, it might be a little difficult to decide where a component should go. In that case, as a thumb rule if a component is accessing the global state .i.e. from the redux store using `useSelector` hook, then it should be listed under `src/containers/{feature}/views` folder. 

Let's add ant design component to the project

```bash
yarn add antd @ant-design/icons
```

Once the process is complete, we'll need to add ant design's CSS to `/src/index.css`. Let's use the dark theme because well, who doesn't love a dark theme.

```tsx
// src/index.css
@import '~antd/dist/antd.dark.css';

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}
```

Let's create **SearchInput** component inside `src/containers/Search/views` where user can search for a topic

```tsx
// src/containers/Search/views/SearchInput.tsx
import React, { FC, useEffect, useState } from "react";
import { Avatar, Input } from "antd";
import logo from "../../../assets/logo.svg";
import "../styles.css";
import { useDispatch, useSelector } from "react-redux";
import { ApplicationState } from "../../../store";
import { getResultsRequest } from "../action";

type Props = {};

const { Search } = Input;
const SearchInput: FC<Props> = (props: Props) => {
  const dispatch = useDispatch();
  const [searchQuery, setSearchQuery] = useState("");
  const [searchQueryLimit, setSearchQueryLimit] = useState(0);

  const isLoading = useSelector<ApplicationState, boolean>(
    (s) => s.search.isLoading
  );

  const onSearchQueryChangeHandler = (
    e: React.ChangeEvent<HTMLInputElement>
  ) => {
    const val = e.target.value;
    setSearchQuery(val);
  };

  const onSearchHandler = () => {
    dispatch(getResultsRequest({
      query: searchQuery,
      limit: searchQueryLimit
    }))
  }

  useEffect(() => {
    setSearchQueryLimit(25);
  }, [])

  return (
    <div className="search-input-container">
      <Avatar src={logo} shape="circle" size={150} />
      <Search
        className="search-input"
        placeholder="Search for a topic"
        loading={isLoading}
        value={searchQuery}
        onChange={onSearchQueryChangeHandler}
        onSearch={onSearchHandler}
      />
    </div>
  );
};

export { SearchInput };
```

Let's start from the top, we have created a functional component **SearchInput.** We are using useSelector and useDispatch hooks to access redux state and dispatch redux actions. We are also using useState hook for managing search query and search query limit locally and useEffect to perform side effects in function components.

From the ant design components library, we have imported **Avatar** and **Input.Search** component. We have also defined some styles in `src/containers/Search/styles.css` and also added [Reddit logo SVG](https://raw.githubusercontent.com/anishkargaonkar/react-reddit-client/master/src/assets/logo.svg) in `src/assets`. 

```css
/* src/containers/Search/styles.css */
.container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}

.search-input-container {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
}

.search-input {
    margin: 2rem 0;
    border-radius: 5px;
}
```

Now import **SearchInput** component in **Search**

```tsx
// src/containers/Search/Search.tsx

import React, { FC } from "react";
import "./styles.css";
import { SearchInput } from "./views/SearchInput";

type Props = {};

const Search: FC<Props> = (props: Props) => {
  return (
    <div className="container">
      <SearchInput />
    </div>
  );
};

export { Search }; 
```

Now hit save and let it compile then navigate to  [`http://localhost:3000`](http://localhost:3000/) you should be able to see something like this

![Screenshot_from_2020-09-22_22-55-00](https://dev-to-uploads.s3.amazonaws.com/i/wijshh399eunrw5skdgb.png)
 
Folder structure so far 

![Untitled 4](https://dev-to-uploads.s3.amazonaws.com/i/3bvv6xf1l5k4fp4yhi6j.png) 

Now let's work on the **Results** component which will show the results from the query. We'll add this component to the `views` folder of the **Search** container. 

Let's create a custom component called **ResultListItem** to display each result. Also, let's add an action type to reset the results which we can use to get back to the starting screen.

```tsx
// src/containers/Search/types.ts

// ... SearchResults model

export interface Search {
  kind: string;
  data: SearchResults;
}

export interface SearchQuery {
  query: string;
  limit: number;
};

interface Errors {
  results: CustomError | null
}

export enum SearchActionTypes {
  GET_RESULTS_REQUEST = "@@search/GET_RESULTS_REQUEST",
  GET_RESULTS_SUCCESS = "@@search/GET_RESULTS_SUCCESS",
  GET_RESULTS_ERROR = "@@search/GET_RESULTS_ERROR",

  **RESET_RESULTS = '@@search/RESET_RESULTS'**
}

export interface SearchState {
  isLoading: boolean,
  results: Search | null,
  errors: Errors
}
```

Here we are adding a `RESET_RESULTS` action type to `src/containers/Search/types.ts` which will be used to reset `results` state to `null` in **SearchState**. 

```tsx
// src/containers/Search/action.ts

import { createAction } from "@reduxjs/toolkit";
import { CustomError } from "../../utils/api-helper";
import { Search, SearchActionTypes, SearchQuery } from "./types";

export const getResultsRequest = createAction<SearchQuery>(
  SearchActionTypes.GET_RESULTS_REQUEST
);

export const getResultsSuccess = createAction<Search>(
  SearchActionTypes.GET_RESULTS_SUCCESS
);

export const getResultsError = createAction<CustomError>(
  SearchActionTypes.GET_RESULTS_ERROR
);

**export const resetResults = createAction(
  SearchActionTypes.RESET_RESULTS
);**
```

Here we add a new action type **resetResults**, notice that we have not defined a return type as we have done for other actions? Since there's no value returned in `resetResultst` there's no need to define an action type.

```tsx
// src/containers/Search/reducer.ts

import { createReducer } from "@reduxjs/toolkit";
import {
  getResultsError,
  getResultsRequest,
  getResultsSuccess,
  resetResults,
} from "./action";
import { SearchState } from "./types";

const initalState: SearchState = {
  isLoading: false,
  results: null,
  errors: {
    results: null,
  },
};

const reducer = createReducer(initalState, (builder) => {
  return builder
    .addCase(getResultsRequest, (state, action) => {
      state.isLoading = true;
      state.results = null;
      state.errors.results = null;
    })
    .addCase(getResultsSuccess, (state, action) => {
      state.isLoading = false;
      state.results = action.payload;
    })
    .addCase(getResultsError, (state, action) => {
      state.isLoading = false;
      state.errors.results = action.payload;
    })
    .addCase(resetResults, (state, action) => {
      state.results = null;
    });
});

export { initalState as searchInitialState, reducer as searchReducer };
```

Adding  a case for `resetResults` in the reducer and set `results` to `null` .i.e. initial state.

Now let's create a **Results** component to display search results.

```tsx
// src/containers/Search/views/Results.tsx
import React, { FC } from "react";
import { useDispatch, useSelector } from "react-redux";
import { ApplicationState } from "../../../store";
import { Search } from "../types";
import { ResultListItem } from "../../../components/ResultListItem/ResultListItem";
import logo from "../../../assets/logo.svg";
import { ArrowLeftOutlined } from "@ant-design/icons";
import { Button } from "antd";
import { resetResults } from "../action";
import "../styles.css";

type Props = {};

const Results: FC<Props> = (props: Props) => {
  const dispatch = useDispatch();
  const results = useSelector<ApplicationState, Search | null>(
    (s) => s.search.results
  );

  const onResetResultsHandler = () => {
    dispatch(resetResults());
  };

  return (
    <div>
      <div className="result-header">
        <Button
          icon={<ArrowLeftOutlined />}
          shape="circle-outline"
          onClick={() => onResetResultsHandler()}
        />
        <div>Search Results</div>
        <div />
      </div>
      {!results || results.data.children.length === 0 ? (
        <div className="no-results-container">No results found</div>
      ) : (
        <div className="results-container">
          {results.data.children.map((result, index) => (
            <ResultListItem
              key={index}
              title={result.data.title}
              imageURL={result.data.thumbnail === "self" ? logo : result.data.thumbnail}
              sourceURL={result.data.permalink}
            />
          ))}
        </div>
      )}
    </div>
  );
};

export { Results };
```

```css
/* src/containers/Search/styles.css */
.container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}

.search-input-container {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
}

.search-input {
    margin: 2rem 0;
    border-radius: 5px;
}

.result-header {
   font-size: 1.5rem;
   display: flex;
   justify-content: space-between;
   align-items: center;
   padding: 0.5rem;
}

.result-header > i {
    cursor: pointer;
}

.results-container {
    max-width: 100vh;
    max-height: 80vh;
    overflow-y: scroll;
}

.no-results-container {
    width: 100vh;
    height: 80vh;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
}
```

Above we have defined a functional component called **Results** and the styles are defined in `src/containers/Search/styles.css`. We are using hooks for getting and resetting redux state `results`. 

Let us now define **ResultListItem** component and it's styles in `src/components/ResultListItem` . The pattern followed here is similar to that of the container. For a component that can be used in multiple places, we define it in a folder called **components** and create a folder with a component name that will contain it's component logic and styles. 

```tsx
// src/components/ResultListItem/ResultListItem.tsx

import React, { FC } from "react";
import "./styles.css";
import logo from "../../assets/logo.svg";

type Props = {
  title: string;
  imageURL: string;
  sourceURL: string;
};

const ResultListItem: FC<Props> = (props: Props) => {
  const { title, imageURL, sourceURL } = props;

  const onClickHandler = (url: string) => {
    window.open(`https://reddit.com/${url}`);
  };

  return (
      <div className="item-container" onClick={() => onClickHandler(sourceURL)}>
          <img className="thumbnail" alt="" src={imageURL} onError={() => logo} />
          <div>
              <div className="title">{title}</div>
          </div>
    </div>
  );
};

export { ResultListItem };
```

```css
/* src/components/ResultListItem/styles.css */
.item-container {
    display: flex;
    align-items: center;
    padding: 0.5rem;
    width: 100%;
    height: 6rem;
    border: 1px solid rgb(77, 77, 77);
    margin-bottom: 0.5rem;
    border-radius: 4px;
    cursor: pointer;
}

.thumbnail {
    width: 5rem;
    border-radius: 0.2rem;
}

.title {
    font-weight: bold;
    padding: 1rem;
}
```

And make the following changes to **Search** container to display **Results** component if search results are present else show **SearchInput** component.

```tsx
// src/containers/Search/Search.tsx
import { message } from "antd";
import React, { FC, useEffect } from "react";
import { useSelector } from "react-redux";
import { ApplicationState } from "../../store";
import { CustomError } from "../../utils/api-helper";
import "./styles.css";
import { Search as SearchModel } from "./types";
import { Results } from "./views/Results";
import { SearchInput } from "./views/SearchInput";

type Props = {};

const Search: FC<Props> = (props: Props) => {
  const results = useSelector<ApplicationState, SearchModel | null>(
    (s) => s.search.results
  );
  const searchError = useSelector<ApplicationState, CustomError | null>(
    (s) => s.search.errors.results
  );

  useEffect(() => {
    if (searchError) {
      message.error(searchError.message);
    }
  }, [searchError]);

  return (
    <div className="container">{!results ? <SearchInput /> : <Results />}</div>
  );
};

export { Search };

```

Finally, your project structure should look something like this with all the above changes

![Untitled 5](https://dev-to-uploads.s3.amazonaws.com/i/1lmhjslepcy2ernleuez.png) 

Once all the above changes are saved, the project should compile and you should be able to search for a topic and see results as shown below

![srp-optimize](https://dev-to-uploads.s3.amazonaws.com/i/gkizlhzgmwi42j56qbsr.gif) 

You can refer to the following repository for the final code.

{% github anishkargaonkar/react-reddit-client no-readme %}

# Closing thoughts

In this 2 part series, I've tried to define a structure that has worked for me with mid/large scale projects where debugging bugs, adding new features with the ever-changing scope was easy and manageable both in React and React-Native. Though there's no perfect structure that works for all, this can be a good starting point.

I hope you enjoyed reading the article as much as I enjoyed writing it. Would love to hear your thoughts about it. Adios!