DEV Community

Cover image for The right way to use Flatlist + TypeScript + Styled Components in React Native
Everaldo Junior
Everaldo Junior

Posted on

The right way to use Flatlist + TypeScript + Styled Components in React Native

1. Scenario

Let’s imagine that you need to implement a list of names using TypeScript + Styled Components, like the image below:

Image description

2. Dependencies

If you haven’t installed styled-components yet, open your terminal inside your typescript React Native project, copy and paste the lines below:

yarn add styled-components
yarn add @types/styled-components -D
Enter fullscreen mode Exit fullscreen mode

3. Implementing without Styled Components

I am going to implement the list without styled-components, so we can change one part per time.
First, I will define the interface of the objects that are going to be in the list and the data of the items that are going to be rendered


export interface IUser {
  id: string;
  name: string;
}

const DATA = [
  {
    id: '1',
    name: 'Michael Scott',
  },
  {
    id: '2',
    name: 'Jim Halpert',
  },
  {
    id: '3',
    name: 'Pam Beesly',
  },
  {
    id: '4',
    name: 'Dwight Schrute',
  },
  {
    id: '5',
    name: 'Andy Bernard',
  },
  {
    id: '6',
    name: 'Ryan Howard',
  },
  {
    id: '7',
    name: 'Kelly Kapoor',
  },
  {
    id: '8',
    name: 'Toby Flenderson',
  },
  {
    id: '9',
    name: 'Stanley Hudson',
  },
  {
    id: '10',
    name: 'Phyllis Vance',
  },
];
Enter fullscreen mode Exit fullscreen mode

Now I’m going to create the Component that will render each IUser item on the list.

const Item = ({data}: {data: IUser}) => (
  <View
    style={{
      backgroundColor: '#eeeeee',
      borderRadius: 10,
      padding: 20,
      marginVertical: 8,
      marginHorizontal: 16,
    }}>
    <Text style={{fontSize: 24}}>{data.name}</Text>
  </View>
);
Enter fullscreen mode Exit fullscreen mode

The first important part here is that you should destruct the data prop that we’re passing to the Item component and type it with the IUser interface.

Finally, I will create the Flatlist component:

const App = () => {
  return (
    <View style={{flex: 1}}>
      <FlatList
        data={DATA}
        renderItem={({item}) => <Item data={item} />}
        keyExtractor={(item: IUser) => item.id}
      />
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

4. Adding Styled Components

Now, we are ready to refactor these components using styled-components.

First, I will create a file and name it styles.ts

Image description

Now, I will create a style for each component that we created previously.

import {FlatList} from 'react-native';
import styled from 'styled-components/native';
import {IUser} from './App';

export const Container = styled.View`
  flex: 1;
`;

export const ItemContainer = styled.View`
  background-color: #eeeeee;
  border-radius: 10px;
  padding: 20px;
  margin-vertical: 8px;
  margin-horizontal: 16px;
`;

export const ItemName = styled.Text`
  font-size: 24px;
`;

export const UsersList = styled.FlatList`
  padding: 20px;
`;
Enter fullscreen mode Exit fullscreen mode

After that, we can replace the components with inline styles with the isolated styles that we just created.


const Item = ({data}: {data: IUser}) => (
  <ItemContainer>
    <ItemName>{data.name}</ItemName>
  </ItemContainer>
);

const App = () => {
  const renderItem: ListRenderItem<IUser> = ({item}) => <Item data={item} />;

  return (
    <Container>
      <UsersList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={(item: IUser) => item.id}
      />
    </Container>
  );
};
Enter fullscreen mode Exit fullscreen mode

But you will see that Typescript will fire an error in your styled-Component created Flatlist

Image description

That’s because you have to declare the type of data that we’re passing to the Flatlist to the styled-component flatlist.

So go to your styles.ts file and change the UsersList style from:

export const UsersList = styled.FlatList`
  padding: 20px;
`;
Enter fullscreen mode Exit fullscreen mode

To:

export const UsersList = styled(FlatList as new () => FlatList<IUser>)`
  padding: 20px;
`;
Enter fullscreen mode Exit fullscreen mode

5. Conclusion and Final Result

Using TypeScript + Flatlists + Styled Components can be tricky sometimes, but I hope that this tutorial helps you somehow.

That’s our final App.tsx file:

import React from 'react';
import {ListRenderItem} from 'react-native';
import {Container, ItemContainer, ItemName, UsersList} from './styles';

export interface IUser {
  id: string;
  name: string;
}

const DATA = [
  {
    id: '1',
    name: 'Michael Scott',
  },
  {
    id: '2',
    name: 'Jim Halpert',
  },
  {
    id: '3',
    name: 'Pam Beesly',
  },
  {
    id: '4',
    name: 'Dwight Schrute',
  },
  {
    id: '5',
    name: 'Andy Bernard',
  },
  {
    id: '6',
    name: 'Ryan Howard',
  },
  {
    id: '7',
    name: 'Kelly Kapoor',
  },
  {
    id: '8',
    name: 'Toby Flenderson',
  },
  {
    id: '9',
    name: 'Stanley Hudson',
  },
  {
    id: '10',
    name: 'Phyllis Vance',
  },
];

const Item = ({data}: {data: IUser}) => (
  <ItemContainer>
    <ItemName>{data.name}</ItemName>
  </ItemContainer>
);

const App = () => {
  const renderItem: ListRenderItem<IUser> = ({item}) => <Item data={item} />;

  return (
    <Container>
      <UsersList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={(item: IUser) => item.id}
      />
    </Container>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

And that’s our final styles.ts file:

import {FlatList} from 'react-native';
import styled from 'styled-components/native';
import {IUser} from './App';

export const Container = styled.SafeAreaView`
  flex: 1;
`;

export const ItemContainer = styled.View`
  background-color: #eeeeee;
  border-radius: 10px;
  padding: 20px;
  margin-vertical: 8px;
  margin-horizontal: 16px;
`;

export const ItemName = styled.Text`
  font-size: 24px;
`;

export const UsersList = styled(FlatList as new () => FlatList<IUser>)`
  padding: 20px;
`;
Enter fullscreen mode Exit fullscreen mode

And here’s the tutorial repository on GitHub.

Top comments (5)

Collapse
 
lucianogmoraesjr profile image
Luciano Moraes Jr.

Yea man! Thanks, this worked for me. Btw, I had some difficulty passing extra props to the components inside the renderItem function, maybe cause I'm a newbie or a stupid idiot (lol), but for those who have the same difficulty here is the solution:

Using the same example code, let's suppose we want a "selected" prop that based on its state value we give a different style to the list item container. We have to pass the "selected" prop to the renderItem function, but the Item component does not expect to receive this prop, so we must indicate to the component that it can receive a "selected" prop like this:

const Item = ({data, selected}: {data: IUser, selected: boolean}) => (
  <ItemContainer selected={selected}>
    <ItemName>{data.name}</ItemName>
  </ItemContainer>
);

const App = () => {
  const [selected, setSelected] = useState(true)

  const renderItem: ListRenderItem<IUser> = ({item}) => <Item selected={selected}  data={item} />;

  return (
    <Container>
      <UsersList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={(item: IUser) => item.id}
      />
    </Container>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

At this point we can retrieve the value of the prop in the styles, but the styled-component "ItemContainer" also doesn't expect to receive a selected property, so we must create an interface with the props it should expect and pass it as generic to the component, like this:

import {FlatList} from 'react-native';
import styled from 'styled-components/native';
import {IUser} from './App';

interface ItemContainerProps {
  selected?: boolean;
}

export const Container = styled.SafeAreaView`
  flex: 1;
`;

export const ItemContainer = styled.View<ItemContainerProps>`
  background-color: ${({ selected }) => (selected ? '#767c9c' : '#eeeeee')};
  border-radius: 10px;
  padding: 20px;
  margin-vertical: 8px;
  margin-horizontal: 16px;
`;

export const ItemName = styled.Text`
  font-size: 24px;
`;

export const UsersList = styled(FlatList as new () => FlatList<IUser>)`
  padding: 20px;
`;
Enter fullscreen mode Exit fullscreen mode

I know it can be simple but I hope it helps someone starting with React Native like me. And sorry if this is not the best way, but it was the solution I found. And don't forget to implement the logic for change the selected item lol

Collapse
 
juniorklawa profile image
Everaldo Junior

Thanks!

Collapse
 
phtn profile image
phtn458 • Edited

//  ->  types.ts

type HeaderProps = {
  title: string
}
type ListProps = {
  title: string
  bg: string
}
type RenderItemProps = {
  item: ItemT
  index: number
}



//  ->  components.tsx

const ListHeaderComponent = ({title}: HeaderProps) => <Text>{title}</Text>
const ListEmptyComponent = () => <></>
const renderItem = (props: RenderItemProps) => <Item {...props} />



//   ->  styled.tsx

const TStyledList = styled(FlatList).attrs<ListProps>(props => ({
  horizontal: true,
  showsHorizontalScrollIndicator: false,
  nestedScrollEnabled: true,
  alwaysBounceHorizontal: true,
  initialNumToRender: 25,
  ListHeaderComponent: <ListHeaderComponent title={props.title} />,
  ListEmptyComponent: <ListEmptyComponent/>
}))`
  background-color: ${({bg}) => bg};
  // or background-color: ${props => props.bg}
`


//  ->  app.tsx

const App = () => (
    <TStyledList
        bg={dark}
        data={[]} 
        renderItem={renderItem} 
        title='TStyled FlatList' 
    />
)



Enter fullscreen mode Exit fullscreen mode
Collapse
 
mbkfa93 profile image
mbkfa93

For me, what seemed to work fine in styled component , was something like


//instead of this
export const UsersList = styled(FlatList as new () => FlatList<IUser>)`
  padding: 20px;
`;


//i used this
export const UsersList = styled(FlatList<IUser>)`
  padding: 20px;
`;


Enter fullscreen mode Exit fullscreen mode
Collapse
 
pierre profile image
Pierre-Henry Soria ✨

Very well explained. Thanks for sharing this Everaldo! 🙂