-
-
Notifications
You must be signed in to change notification settings - Fork 23
Fix no cards loading #73
Changes from all commits
8f64228
5526f93
0087acd
8e55962
e7016de
807e5cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,63 +1,58 @@ | ||
| import React from "react" | ||
| import PropTypes from "prop-types" | ||
| import { createStoreObject } from "effector" | ||
| // @flow | ||
| import * as React from "react" | ||
| import { createComponent } from "effector-react" | ||
| import styled from "styled-components" | ||
|
|
||
| import { ConditionalList } from "@howtocards/ui" | ||
| import { cardsFetching } from "../model/home" | ||
| import { $registry, getCard } from "../model/registry.store" | ||
| import { setUsefulMark } from "../model/registry.events" | ||
| import { CardSkeleton } from "./card-skeleton" | ||
| import { type Card } from "../types" | ||
| import { CardItem } from "./card-item" | ||
|
|
||
| const onUsefulClick = (cardId) => { | ||
| const handleUsefulClick = (cardId) => { | ||
| const card = getCard(cardId) | ||
| if (card) { | ||
| setUsefulMark({ cardId: card.id, isUseful: !card.meta.isUseful }) | ||
| } | ||
| } | ||
|
|
||
| const selectCards = (props) => | ||
| createStoreObject({ | ||
| cards: $registry.map((reg) => props.ids.map((id) => reg[id])), | ||
| isLoading: cardsFetching.isLoading, | ||
| }) | ||
| $registry.map((reg) => props.ids.map((id) => reg[id])) | ||
|
|
||
| export const CardsList = createComponent( | ||
| type Props = { | ||
| ids: number[], | ||
| renderCard?: (param: { card: Card, onUsefulClick: () => * }) => React.Node, | ||
| renderEmpty?: () => React.Node, | ||
| } | ||
|
|
||
| export const CardsList = createComponent<Props, Card[]>( | ||
| selectCards, | ||
| ({ renderCard }, { cards, isLoading }) => { | ||
| return ( | ||
| <> | ||
| {isLoading ? ( | ||
| <> | ||
| <CardSkeleton /> | ||
| <CardSkeleton /> | ||
| <CardSkeleton /> | ||
| </> | ||
| ) : ( | ||
| <ConditionalList | ||
| list={cards} | ||
| renderExists={(list) => ( | ||
| <CardsItemsBlock> | ||
| {list.filter(Boolean).map((card) => | ||
| renderCard({ | ||
| card, | ||
| onUsefulClick: () => onUsefulClick(card.id), | ||
| }), | ||
| )} | ||
| </CardsItemsBlock> | ||
| )} | ||
| /> | ||
| )} | ||
| </> | ||
| ) | ||
| }, | ||
| ({ renderCard = defaultCardRender, renderEmpty = emptyRenderer }, cards) => ( | ||
| <ConditionalList | ||
| list={cards} | ||
| renderExists={(list) => ( | ||
| <CardsItemsBlock> | ||
| {list.filter(Boolean).map((card) => | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case you will have array with empty elements There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Array of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is eager optimization |
||
| renderCard({ | ||
| card, | ||
| onUsefulClick: () => handleUsefulClick(card.id), | ||
| }), | ||
| )} | ||
| </CardsItemsBlock> | ||
| )} | ||
| renderEmpty={renderEmpty} | ||
| /> | ||
| ), | ||
| ) | ||
|
|
||
| CardsList.propTypes = { | ||
| ids: PropTypes.arrayOf(PropTypes.number).isRequired, | ||
| renderCard: PropTypes.func.isRequired, | ||
| } | ||
| const emptyRenderer = () => <p>No cards in that list</p> | ||
| const defaultCardRender = ({ | ||
| card, | ||
| onUsefulClick, | ||
| }: { | ||
| card: Card, | ||
| onUsefulClick: () => *, | ||
| }) => <CardItem key={card.id} card={card} onUsefulClick={onUsefulClick} /> | ||
|
|
||
| export const CardsItemsBlock = styled.div` | ||
| display: flex; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,5 @@ | ||
| // @flow | ||
| export { CardItem } from "./card-item" | ||
| export { CardSkeleton } from "./card-skeleton" | ||
| export { CardsList } from "./cards-list" | ||
| export { SkeletonList } from "./skeleton-list" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // @flow | ||
| import * as React from "react" | ||
| import { CardsList } from "./cards-list" | ||
| import { CardSkeleton } from "./card-skeleton" | ||
|
|
||
| type Props = { | ||
| isLoading: boolean, | ||
| ids: number[], | ||
| count?: number, | ||
| renderEmpty: () => React.Node, | ||
| renderCard?: *, | ||
| } | ||
|
|
||
| export const SkeletonList = ({ | ||
| isLoading, | ||
| ids, | ||
| count = 3, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. magic numbers. Need to move to named const |
||
| renderEmpty, | ||
| renderCard, | ||
| }: Props) => | ||
| isLoading ? ( | ||
| <> | ||
sergeysova marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| {Array.from({ length: count }, (_, idx) => ( | ||
| <CardSkeleton key={idx} /> | ||
| ))} | ||
| </> | ||
| ) : ( | ||
| <CardsList ids={ids} renderEmpty={renderEmpty} renderCard={renderCard} /> | ||
| ) | ||
|
|
||
| SkeletonList.defaultProps = { | ||
| count: undefined, | ||
sergeysova marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| renderCard: undefined, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,40 +1,51 @@ | ||
| // @flow | ||
| /* eslint-disable import/no-duplicates */ | ||
| import React from "react" | ||
| import * as React from "react" | ||
| import PropTypes from "prop-types" | ||
| import { useStore } from "effector-react" | ||
| import { distanceInWordsToNow } from "date-fns" | ||
|
|
||
| import { Col, Row } from "lib/styled-components-layout" | ||
| import { Col, Row } from "@lib/styled-components-layout" | ||
| import { Text } from "@howtocards/ui" | ||
| import { CardsCommonTemplate } from "../templates/common" | ||
| import { cardLoading, $card } from "../model/view" | ||
| import { CardItem, CardsList } from "../organisms" | ||
| import { CardItem, CardSkeleton, CardsList } from "../organisms" | ||
|
|
||
| export const CardViewPage = ({ match }) => { | ||
| type Props = { | ||
| match: { | ||
| params: { | ||
| cardId: string, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| export const CardViewPage = ({ match }: Props) => { | ||
| const cardId = parseInt(match.params.cardId, 10) | ||
|
|
||
| React.useEffect(() => { | ||
| cardLoading({ cardId }) | ||
| }, [cardId]) | ||
|
|
||
| const current = useStore($card) | ||
|
|
||
| if (!current) { | ||
| return <p>Loading...</p> | ||
| } | ||
|
|
||
| return ( | ||
| <CardsCommonTemplate sidebar={<Sidebar card={current} />}> | ||
| <CardsList | ||
| ids={[current.id]} | ||
| renderCard={({ card, onUsefulClick }) => ( | ||
| <CardItem | ||
| maximized | ||
| key={card.id} | ||
| card={card} | ||
| onUsefulClick={onUsefulClick} | ||
| /> | ||
| )} | ||
| /> | ||
| {current ? ( | ||
| <CardsList | ||
| key={`card-view-${current.id}`} | ||
| ids={[current.id]} | ||
| renderCard={({ card, onUsefulClick }) => ( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That function can be static (defined in the upper scope) |
||
| <CardItem | ||
| key={card.id} | ||
| maximized | ||
| card={card} | ||
| onUsefulClick={onUsefulClick} | ||
| /> | ||
| )} | ||
| /> | ||
| ) : ( | ||
| <CardSkeleton /> | ||
| )} | ||
| </CardsCommonTemplate> | ||
| ) | ||
| } | ||
|
|
@@ -50,13 +61,26 @@ CardViewPage.propTypes = { | |
| const Sidebar = ({ card }) => ( | ||
| <Col gap="3rem"> | ||
| <Row> | ||
| <Text> | ||
| Created {distanceInWordsToNow(card.createdAt, { addSuffix: true })} | ||
| </Text> | ||
| {card ? ( | ||
| <Text title={createdAt(card)}> | ||
| Created {distanceInWordsToNow(card.createdAt, { addSuffix: true })} | ||
| </Text> | ||
| ) : ( | ||
| <p> </p> | ||
| )} | ||
| </Row> | ||
| </Col> | ||
| ) | ||
|
|
||
| Sidebar.propTypes = { | ||
| card: PropTypes.shape({}).isRequired, | ||
| card: PropTypes.shape({}), | ||
| } | ||
|
|
||
| Sidebar.defaultProps = { | ||
| card: null, | ||
| } | ||
|
|
||
| function createdAt(card) { | ||
| const date = new Date(card.createdAt) | ||
| return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}` | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.