how do I set the number of cards per row? I need 4 cards to be in one row of mine.
I have been trying for hours.. can't figure out what i do wrong..
I was getting all sorts of errors when i played around with this.
like this one :
here
my package.json file:
thanks!
:)
import { React } from 'react';
import { Card, Button, CardGroup } from 'react-bootstrap';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
const MyDishes = props => {
const { dishes } = props;
return (
<CardGroup style={{display: 'flex', flexDirection: 'row'}}>
{dishes.length > 0 &&
dishes.map(d => (
<Row xs={1} md={2} className="g-4">
{Array.from({ length: 4 }).map((_, idx) => (
<Col>
<Card key={d.id} style={{ width: '100em' }} style={{flex: 1}}>
<Card.Img variant="top" src={d.attributes.picture} />
<Card.Body>
<Card.Title>{d.attributes.name}</Card.Title>
<Card.Text>Possibly some text here</Card.Text>
<Link to={`/dishes/${d.id}`}>
<Button variant="primary">Full Recipe</Button>
</Link>
</Card.Body>
</Card>
</Col>
))}
</Row>
</CardGroup>
);
};
const mapStateToProps = state => {
return {
dishes: state.myDishes
};
};
export default connect(mapStateToProps)(MyDishes);
i get this error now :
To use bootstrap layout system, must use <Container> to wrap <Row> and <Col>
You don't need custom styles to achieve that you want.
import { React } from 'react';
import { Container, Card, Button, CardGroup } from 'react-bootstrap';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
const MyDishes = props => {
const { dishes } = props;
return (
<Container>
{dishes.length > 0 &&
dishes.map(d => (
<Row xs={1} md={2} className="g-4">
{Array.from({ length: 4 }).map((_, idx) => (
<Col>
<Card key={d.id}>
<Card.Img variant="top" src={d.attributes.picture} />
<Card.Body>
<Card.Title>{d.attributes.name}</Card.Title>
<Card.Text>Possibly some text here</Card.Text>
<Link to={`/dishes/${d.id}`}>
<Button variant="primary">Full Recipe</Button>
</Link>
</Card.Body>
</Card>
</Col>
))}
</Row>
))}
</Container>
);
};
const mapStateToProps = state => {
return {
dishes: state.myDishes
};
};
export default connect(mapStateToProps)(MyDishes);
Related
I have a component, GameGrid which makes an axios call to get the data that I pass as props to another component, GameCard. I currently have a Header component that is in the same file as GameGrid. The header has buttons which when clicked, changes the endpoint that the GameGrid is calling. I am trying to figure out a way to separate the two components, Header and GameGrid, but still have the buttons on the header determine which endpoint the GameGrid should call. Also, is the GameGrid the right place to have the data being called?
GameGrid.jsx
import React, { useEffect, useState } from "react";
import AppBar from '#mui/material/AppBar';
import Toolbar from '#mui/material/Toolbar';
import Box from '#mui/material/Box';
import IconButton from '#mui/material/IconButton';
import Grid from '#mui/material/Grid';
import axios from "axios";
import GameCard from "./GameCard";
export default function GameGrid() {
const [data, setData] = useState(); // Data Hook
const [route, setRoute] = useState('/')
useEffect(() => {
const getData = async () => {
try {
const { data } = await axios.get(`${process.env.REACT_APP_URL}${route}`);
console.log(data);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [route]);
const createCards = (data) => {
return <GameCard
key = {data.id}
id = {data.id}
url = {data.game_url}
thumbnail = {data.thumbnail}
title = {data.title}
platform = {data.platform}
description = {data.short_description}
genre = {data.genre}
publisher = {data.publisher}
developer = {data.developer}
/>;
}
const Header = () => {
return (
<Box sx ={{ flexGrow: 1 }}>
<AppBar position="fixed">
<Toolbar>
<Grid sx={{ flexGrow: 1,
border: '1px solid white'}} container spacing={2}>
<Grid sx={{
}}>
<IconButton onClick={() => setRoute('/')}>
<img src='computer-mouse.png' alt='Logo' id='icon'/>
</IconButton>
<IconButton onClick={() => setRoute('/shooter')}>
<img src="shooter.png" alt="Shooter Games" />
</IconButton>
</Grid>
<Grid>
</Grid>
</Grid>
</Toolbar>
</AppBar>
</Box>
)
}
return (
<>
<Header/>
<div className="card-container">
{data?.map(createCards)}
</div>
</>
)
}
GameCard.jsx
import React from "react";
import Card from "#mui/material/Card";
import CardContent from "#mui/material/CardContent";
import CardMedia from "#mui/material/CardMedia";
import Typography from "#mui/material/Typography";
import { CardActionArea, Chip } from "#mui/material";
import Stack from '#mui/material/Stack';
export default function GameCard(props) {
return (
<Card sx={{ width: 550, margin: 2}} key={props.id} id={props.id} >
<CardActionArea href={props.url}>
<CardMedia
component="img"
height="fit-content"
image={props?.thumbnail}
/>
<CardContent>
<Stack direction="row" justifyContent="space-between">
<Typography gutterBottom variant="h6" component="div">
{props.title}
</Typography>
<Chip label={props.platform} size="small"/>
</Stack>
<Typography gutterBottom variant="body1" id="description-text"
color="text.secondary"
sx={{
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap'
}}>
{props.description}
</Typography>
<Stack direction="row" justifyContent="space-between">
<Chip label={props.genre} size="small" />
<Chip label={props.publisher} size="small" />
<Chip label={props.developer} size="small" />
</Stack>
</CardContent>
</CardActionArea>
</Card>
);
}
To separate Header, simply define it with all the props it requires. In this instance, that appears to be the setRoute function though you can call it whatever you like.
For example
// Header.jsx
const Header = ({ onRouteClick }) => (
<Box sx={{ flexGrow: 1 }}>
{/* ... */}
<IconButton onClick={() => onRouteClick("/")}>
<img src="computer-mouse.png" alt="Logo" id="icon" />
</IconButton>
<IconButton onClick={() => onRouteClick("/shooter")}>
<img src="shooter.png" alt="Shooter Games" />
</IconButton>
{/* ... */}
</Box>
);
export default Header;
and in your GameGrid component, import Header from "./Header" and use...
<Header onRouteClick={setRoute} />
If you were using Typescript (highly recommended), the API for Header would look something like this
interface HeaderProps {
onRouteClick: (route: string) => void;
}
const Header = ({ onRouteClick }: HeaderProps) => (
// ...
);
I am having an issue with Navigation Bar jumping to the top after selecting specific page. I want to side bar to stay same position after selecting the page. I found few similar questions here but the given solutions didn't solve my issue. Here the Code.
SideBarItem.js
import React, { useState } from 'react';
import classnames from 'classnames';
import { useRouter } from 'next/router';
import Link from 'next/link';
import useTranslation from 'next-translate/useTranslation';
import { AiOutlineLeft, AiOutlineDown } from 'react-icons/ai';
const SidebarHeader = ({ toggleSideBar, transKey }) => {
const { t } = useTranslation();
return (
<li
name={transKey}
id={transKey}
className={classnames('nav-small-cap', { 'dots-icon': !toggleSideBar })}>
{!toggleSideBar && <i className="mdi mdi-dots-horizontal"></i>}
{toggleSideBar && <span className="hide-menu">{t(transKey)}</span>}
</li>
);
};
const SidebarItem = ({
route,
icon,
toggleSideBar,
transKey,
isHeader,
subs,
}) => {
const { t } = useTranslation();
const router = useRouter();
const [isOpen, setIsOpen] = useState(false);
const handleToggle = () => {
setIsOpen(!isOpen);
};
const selectedClassName = router.pathname === route ? 'selected' : null;
return (
<>
{isHeader && (
<SidebarHeader
toggleSideBar={toggleSideBar}
transKey={transKey}
id={transKey}
/>
)}
{!isHeader && (
<li className={classnames('sidebar-item', selectedClassName)}>
<div className="sidebar-link clickable text-white-100 waves-effect waves-dark">
<i className={icon} style={{ color: 'white' }}></i>
{toggleSideBar && (
<div className="arrow-style">
{!subs && (
<Link href={route}>
<a className="hide-menu"> {t(transKey)}</a>
</Link>
)}
{subs && (
<>
<a className="hide-menu" onClick={handleToggle}>
{t(transKey)}
</a>
<div className="sidebar-arrow">
{isOpen ? <AiOutlineDown /> : <AiOutlineLeft />}
</div>
</>
)}
</div>
)}
</div>
{subs && (
<ul
className={classnames('collapse', 'first-level', {
show: isOpen,
})}>
{subs.map((subNavItem) => (
<SidebarItem
key={subNavItem.transKey}
{...subNavItem}
toggleSideBar={toggleSideBar}
/>
))}
</ul>
)}
</li>
)}
</>
);
};
export default SidebarItem;
SidebarLinks.js file
import React from 'react';
import navItems from '#constants/navItems';
import SidebarItem from './SidebarItem';
const SidebarLinks = (props) => {
return (
<nav className="sidebar-nav">
<ul id="sidebarnav" className="in">
{navItems.map((navItem) => (
<SidebarItem key={navItem.transKey} {...navItem} {...props} />
))}
</ul>
</nav>
);
};
export default SidebarLinks;
LayoutWithSidebar.js file
import React from 'react';
import useTranslation from 'next-translate/useTranslation';
import SidebarFooter from '#components/Sidebar/SidebarFooter';
import UserProfile from '#components/Sidebar/UserProfile';
import SidebarLinks from '#components/Sidebar/SidebarLinks';
import Styles from './styles';
const LayoutWithSidebar = ({ toggleSideBar, setToggleSidebar, btn }) => {
const { t } = useTranslation();
const handleEnterMouse = () => {
setToggleSidebar(true);
};
return (
<aside
className={`left-sidebar-style ${
toggleSideBar ? 'open-left-sidebar' : 'left-sidebar'
}`}
onMouseEnter={handleEnterMouse}
data-sidebarbg="skin5">
<div className="scroll-sidebar">
<UserProfile btn={btn} toggleSideBar={toggleSideBar} translation={t} />
<SidebarLinks btn={btn} toggleSideBar={toggleSideBar} translation={t} />
</div>
{(toggleSideBar || btn) && <SidebarFooter />}
<style jsx toggleSideBar={toggleSideBar}>
{Styles}
</style>
</aside>
);
};
export default LayoutWithSidebar;
App.js file
import React, { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { Row, Col } from 'reactstrap';
import useTranslation from 'next-translate/useTranslation';
import AccountOverview from '#components/Dashboard/AccountOverview';
import Exposure from '#components/Dashboard/Exposure';
import PerformanceStats from '#components/Dashboard/PerformanceStats';
import CommissionAndBalance from '#components/Dashboard/CommissionAndBalance';
import MostRecentConversions from '#components/Dashboard/MostRecentConversions/index';
import Announcement from '#components/Dashboard/Announcement';
import TopPublishers from '#components/Dashboard/TopPublishers';
import ClickByCountries from '#components/Dashboard/TotalVisits/index';
import ClickPerDevice from '#components/Dashboard/OurVisitors/index';
import useShallowEqualSelector from '#hooks/useShallowEqualSelector';
import BaseLayout from '#layouts/BaseLayout';
import { formatNumber } from '#utils';
import routes from '#constants/routes';
import {
fetchAccountOverviewDataAction,
fetchPerformanceStatAction,
fetchAnnouncementAction,
fetchCommissionBalanceAction,
fetchExposuresAction,
fetchRecentConversionAction,
fetchTopPublishersAction,
fetchVisitorDataAction,
} from '#actions';
const Dashboard = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const [announcementRole, setAnnouncementRole] = useState('advertiser');
const [statFilter, setStatFilter] = useState('day');
const {
accountOverview,
exposures,
visitors,
commissionBalance,
recentConversions,
topPublishers,
performanceStatistics,
} = useShallowEqualSelector(({ stat }) => {
return stat;
});
const {
fetch: { list: announcements, status: announcementFetchStatus },
} = useShallowEqualSelector((state) => state.announcements);
const {
get: { currencyCode },
} = useShallowEqualSelector((state) => state.stat?.recentConversions);
useEffect(() => {
dispatch(fetchCommissionBalanceAction());
dispatch(fetchRecentConversionAction());
}, [dispatch]);
useEffect(() => {
dispatch(fetchAccountOverviewDataAction());
dispatch(fetchExposuresAction());
dispatch(fetchTopPublishersAction());
dispatch(fetchVisitorDataAction());
}, [dispatch]);
useEffect(() => {
dispatch(fetchPerformanceStatAction({ filter: statFilter }));
}, [dispatch, statFilter]);
useEffect(() => {
dispatch(
fetchAnnouncementAction({ role: announcementRole, page: 1, perPage: 4 })
);
}, [dispatch, announcementRole]);
return (
<BaseLayout
metaPageTitle={t('dashboard:pageTitle')}
pageTitle={t('dashboard:pageTitle')}
pageSubtitle={t('dashboard:pageSubtitle')}
breadcrumbPaths={[
{ url: routes.dashboard.url, text: t('common:home') },
{ text: t('dashboard:pageTitle') },
]}>
<div className="container-fluid">
{
<Row>
<Col sm={12}>
<AccountOverview
accountOverview={accountOverview.get.data}
isLoading={accountOverview.get.status === 'loading'}
t={t}
/>
</Col>
</Row>
}
{
<Row>
<Col sm={12}>
<PerformanceStats
isLoading={performanceStatistics.get.status === 'loading'}
setStatFilter={setStatFilter}
statFilter={statFilter}
performanceStatistics={performanceStatistics.get.data}
t={t}
/>
</Col>
</Row>
}
{
<Row>
<Col sm={12}>
<Exposure
exposures={exposures.get.data}
isLoading={exposures.get.status === 'loading'}
t={t}
/>
</Col>
</Row>
}
{
<Row>
<Col sm={12}>
<CommissionAndBalance
commissionBalance={commissionBalance.get.data}
isLoading={commissionBalance.get.status === 'loading'}
t={t}
/>
</Col>
</Row>
}
<Row>
<Col sm={12}>
<MostRecentConversions
isLoading={recentConversions.get.status === 'loading'}
recentConversions={recentConversions.get.data}
t={t}
currencyCode={currencyCode}
/>
</Col>
</Row>
<Row>
<Col sm={12}>
<div className="card-deck mb-4">
{
<Announcement
announcements={announcements}
announcementRole={announcementRole}
isLoading={announcementFetchStatus === 'loading'}
setAnnouncementRole={setAnnouncementRole}
t={t}
/>
}
<TopPublishers
isLoading={topPublishers.get.status === 'loading'}
topPublishers={topPublishers.get.data}
t={t}
formatNumber={formatNumber}
format
/>
</div>
</Col>
</Row>
{
<Row>
<Col sm={6}>
<ClickByCountries
isLoading={visitors.get.status === 'loading'}
totalVisitors={visitors.get.data?.totalVisitors}
t={t}
/>
</Col>
<Col sm={6}>
<ClickPerDevice
isLoading={visitors.get.status === 'loading'}
ourVisitors={visitors.get.data?.ourVisitors}
t={t}
/>
</Col>
</Row>
}
</div>
</BaseLayout>
);
};
export default Dashboard;
It would be great if you can assist.
P.S. I already looked through some answers here but it didn't work for me.
PLS check video link below
https://www.youtube.com/shorts/4xzlBfhQcKQ
From the video that you showed and the code you shared it seems that you wrapped your sidebar along with the entire application and pages components. If you want to avoid "rerendering" the sidebar, which is the thing that applies the "go to the top" effect, you should unwrap the layout with sidebar from the route changing, this will prevent this behaviour.
I would like to make it possible that whenever I click on a button (which is a component itself) a modal component is called. I did some research but none of the solutions is doing what I need. It partially works but not quite the way I want it to because, once the state is set to true and I modify the quantity in the Meals.js and click again the modal doesn't show up
Meals.js
import React, { useState } from "react";
import { Card } from "react-bootstrap";
import { Link } from "react-router-dom";
import NumericInput from "react-numeric-input";
import AddBtn from "./AddtoCartBTN";
function Meals({ product }) {
const [Food, setFood] = useState(0);
return (
<Card className="my-3 p-3 rounded">
<Link to={`/product/${product.id}`}>
<Card.Img src={product.image} />
</Link>
<Card.Body>
<Link to={`/product/${product.id}`} style={{ textDecoration: "none" }}>
<Card.Title as="div">
<strong>{product.name}</strong>
</Card.Title>
</Link>
Quantity{" "}
<NumericInput min={0} max={100} onChange={(value) => setFood(value)} />
{/* <NumericInput min={0} max={100} onChange={ChangeHandler(value)} /> */}
<Card.Text as="h6" style={{ color: "red" }}>
{Food === 0 ? product.price : Food * product.price} CFA
</Card.Text>
<AddBtn quantity={Food} price={product.price} productId={product.id} />
</Card.Body>
</Card>
);
}
export default Meals;
AddToCartBtn.js
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { addToCart } from "../actions/cmdActions";
import toast, { Toaster } from "react-hot-toast";
import CartModal from "./CartModal";
function AddtoCartBTN({ quantity, price, productId }) {
const AddedPrice = quantity * price;
const [showModal, setShowModal] = useState(false)
const notify = () => toast("Ajout Effectue !", {});
const dispatch = useDispatch();
const AddtoCart = () => {
// console.log("Added to cart !");
// console.log("Added : ", AddedPrice);
if (AddedPrice > 0) {
dispatch(addToCart(productId, AddedPrice));
notify();
setShowModal(true)
}
};
return (
<button
className="btn btn-primary d-flex justify-content-center"
onClick={AddtoCart}
>
Ajouter
{showModal && <CartModal/>}
<Toaster />
</button>
);
}
export default AddtoCartBTN;
Modal.js
import React, { useState } from "react";
import { Modal, Button, Row, Col } from "react-bootstrap";
import { useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
function CartModal () {
const [modalShow, setModalShow] = useState(true);
let history = useHistory();
const panierHandler = () => {
history.push('/commander')
}
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
function MyVerticallyCenteredModal(props) {
return (
<Modal
{...props}
size="md"
aria-labelledby="contained-modal-title-vcenter"
centered
backdrop="static"
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
<b style={{ color: "red" }}> choix de produits</b> <br />
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Row>
<Col><b>Nom</b></Col>
<Col><b>Quantite</b></Col>
</Row><hr/>
{cartItems && cartItems.map((item,i)=>(
<>
<Row key={i}>
<Col>{item.name}</Col>
<Col>{item.total / item.price}</Col>
</Row><hr/>
</>
))}
</Modal.Body>
<Modal.Footer
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Button
// onClick={props.onHide}
onClick={panierHandler}
>
Aller au Panier
</Button>
</Modal.Footer>
</Modal>
);
}
return (
<>
<MyVerticallyCenteredModal
show={modalShow}
onHide={() => setModalShow(false)}
/>
</>
);
}
export default CartModal
in AddToCartBtn.js, you can pass setShowModal as a prop.
{showModal && <CartModal setShowModal={setShowModal} />}
And the, in Modal.js, you can use the prop setShowModal to set the value to false
<MyVerticallyCenteredModal show={true} onHide={() => setShowModal(false)} />
Eliminate the state in Modal.js, keep visible as always true
I creating simple app. It must be app with information about pokemons. So I need to create, when user click on pokeCard, Sidebar info.
How it look now:
So, Sidebar is must to be, it can be, for example, only white background.
I think about styled-components, but I not sure, that this would be the right decision
How to do it with functional Component?
Wrapper
const [SelectedPokemonIndex, setSelectedPokemonIndex] = useState();
return (
<Row>
<Col xs={24} sm={14} lg={16}>
<Pokemons
PokemonsList={PokemonsList}
loadMoreItems={loadMoreItems}
Loading={Loading}
onClickPoke={(pokemonId) => {
fetchPokemonDetails(pokemonId);
fetchPokemon(pokemonId);
fetchPokemonStats(pokemonId);
setSelectedPokemonIndex(pokemonId);
}}
/>
</Col>
<Col xs={24} sm={10} lg={8}>
<About
pokemon={SelectedPokemon}
PokemonTypes={PokemonTypes}
PokemonStats={PokemonStats}
index={SelectedPokemonIndex}
LoadingForSelectedPokemon={LoadingForSelectedPokemon}
/>
</Col>
</Row>
);
}
export default Wrapper;
Child component of wrapper
function Pokemons(props) {
let { PokemonsList, loadMoreItems, Loading, onClickPoke } = props;
return (
<GridCard
image={`${IMAGE_BASE_URL}${++index}.png`}
pokemonId={index}
pokemonName={pokemon.name}
pokemonUrl={pokemon.url}
onClickPoke={onClickPoke}
/>
PokeCard
import React, { useEffect, useState } from "react";
import { Col, Typography } from "antd";
import "./GridCards.css";
const { Title } = Typography;
function GridCards(props) {
let { key, image, pokemonName, pokemonUrl, pokemonId } = props;
return (
<Col
key={key}
lg={8}
md={12}
xs={24}
onClick={() => {
props.onClickPoke(pokemonId);
}}
>
<div
className="poke-card"
}}
>
<img alt={pokemonName} src={image} />
{LoadingForPokemon && <div>Loading...</div>}
</div>
</Col>
);
}
export default GridCards;
This is Sidebar, what must to be change:
function About(props) {
let {
pokemon,
LoadingForSelectedPokemon,
index,
PokemonTypes,
PokemonStats,
} = props;
return (
<div
style={{
position: "sticky",
top: 0,
display: "flex",
justifyContent: "center",
}}
>
<PokemonDetails
pokemonName={pokemon.name}
pokemonId={pokemon.id}
pokemon={pokemon}
LoadingForSelectedPokemon={LoadingForSelectedPokemon}
image={`${IMAGE_BASE_URL}${index}.png`}
PokemonTypes={PokemonTypes}
PokemonStats={PokemonStats}
/>
</div>
);
}
This is a pretty broad question and there's a lot going on in your code, but I think you need to move some state management around.
In your GridCards component, give it a prop called onCardClick and call that function in the onClick of the <Col> component you're using. It'll look something like this:
function GridCard(props) {
const { key, image, pokemonName, pokemonUrl, pokemonId, onCardClick } = props;
return (
<Col
key={key}
lg={8}
md={12}
xs={24}
onClick={() => onCardClick()}
>
<div
className="poke-card"
>
<img alt={pokemonName} src={image} />
{LoadingForPokemon && <div>Loading...</div>}
</div>
</Col>
);
}
export default GridCard;
Then in your wrapper components, instead of using the Pokemons component, I think you can just use your GridCard component and map on whatever data you're using to render out the cards currently.
So something like this:
export default function Wrapper() {
const [selectedPokemon, setSelectedPokemon] = useState(null);
// data is whatever you have to iterate over to put data into your cards
const pokemonCards = data.map((p, idx) => {
return (
<GridCard
key={idx}
image={p.image}
pokemonName={p.pokemonName}
onCardClick={() => setSelectedPokemon(p)}
></GridCard>
);
});
return (
<Row>
<Col xs={24} sm={14} lg={16}>
{pokemonCards}
</Col>
<Col xs={24} sm={10} lg={8}>
<About pokemon={selectedPokemon} />
</Col>
</Row>
);
}
Basically, what you should try and accomplish is letting each GridCard component contain all the necessary information for your About component, so that when you click on a GridCard your Wrapper's state can update with a selectedPokemon and then pass that back down into your About component.
I found solution for my question.
Its only need to set isAboutShown
const [isAboutShown, setAboutShow] = useState(false);
And onClick I coding setAboutShow(true)
How to display component?
{isAboutShown && (
<About/>)
I have a problem if on page 1 and after (except 2) what I search (type) if there is data it will display the data that I search (type), see the picture below:
the problem is if on page 2, what I search for will appear on another page. even though on page 2 the data is there, :
This is page 2 before searching :
and this is page 1 before searching
if I'm on page 2 there is data that appears abc and dcs. if I search for dcs then dcs appear or if I type d it will appear d and if on other pages there is also data it will display
I use this codesanbox.io url:
https: //codesandbox.io/embed/condescending-pine-d7q3v
and this my code:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import dummyQnA from "./dummyQnA.json";
import "./styles.css";
import { Row, Col, Pagination, Button, Table, Divider } from "antd";
import Search from "antd/lib/input/Search";
import "antd/dist/antd.css";
const columns = [
{
title: "Question",
dataIndex: "questionAndAnswer",
key: "questionAndAnswer"
}
];
class App extends Component {
constructor(props) {
super(props);
this.state = {
search: ""
};
}
getHighlightedText(text, higlight) {
if (!higlight.trim()) {
return <span>{text}</span>;
}
let parts = text.split(new RegExp(`(${higlight})`, "gi"));
return (
<span>
{parts
.filter(part => part)
.map((part, i) => (
<mark
key={i}
style={
part.toLowerCase() === higlight.toLowerCase()
? { backgroundColor: "yellow" }
: {}
}
>
{part}
</mark>
))}
</span>
);
}
renderQnA = (qna, i) => {
return {
key: i,
questionAndAnswer: [
<Col key={i} md={24} style={{ marginTop: 20 }}>
<Row>
<Col md={23}>
<b>
{" "}
{this.getHighlightedText(qna.questionAdmin, this.state.search)}
</b>
<p style={{ color: "#417505" }}>
{" "}
{this.getHighlightedText(qna.answerCustomer, this.state.search)}
</p>
</Col>
</Row>
</Col>
]
};
};
onChange = e => {
this.setState({ search: e.target.value });
};
render() {
const { search } = this.state;
const lowercasedFilter = search.toLowerCase();
const filteredQnA = dummyQnA.filter(item => {
return Object.keys(item).some(key =>
item[key].toLowerCase().includes(lowercasedFilter)
);
});
return (
<div className="product-forum">
<Row className="title-inline">
<Col md={13} style={{ paddingTop: "6px" }}>
<span className="title-inline__title">
Pertanyaan terkait Produk ({dummyQnA.length})
</span>
</Col>
<Col md={3}>
<Button
type="link"
className="title-inline__button-live-chat"
onClick={this.handleLiveChat}
>
LIVE CHAT
</Button>
</Col>
<Col md={8}>
<Search
value={this.state.search}
placeholder="Cari pertanyaan terkait"
onChange={this.onChange}
/>
</Col>
</Row>
<Table
className="table-qna"
showHeader={false}
pagination={{ defaultPageSize: 5 }}
dataSource={filteredQnA.map((QnA, i) => this.renderQnA(QnA, i))}
columns={columns}
/>
<Divider />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);