changing state of individual react component - javascript

Im new to React and started working on a memory game where you flip cards and compare two cards. Im having trouble understanding how to change state of individual component. now when I click a component the state of all components change and all my cards turn red instead of one. later I was thinking to add photos but for now just testing with background color. Also I know I have to add some logic/features but cant get past state problem.
App.js
import React, {Component} from 'react';
import './App.css';
import Grid from './grid/grid';
import Header from './Header/header';
import Footer from './Footer/footer';
class App extends Component {
cards = [{id:1, name: 'dog'},{id:2, name: 'dog'},{id:3, name: 'cat'},{id:4, name: 'cat'},{id:5, name: 'mouse'},{id:6, name: 'mouse'},{id:7, name: 'horse'},{id:8, name: 'horse'},
{id:9, name: 'pig'},{id:10, name: 'pig'},{id:11, name: 'chicken'},{id:12, name: 'chicken'},{id:13, name: 'cow'},{id:14, name: 'cow'},{id:15, name: 'fox'},{id:16, name: 'fox'}]
.sort( () => Math.random() - 0.5);
clicks = [];
state = {
current: 0,
}
clickHandler = (click) => {
this.clicks.push(click.name);
this.setState({
current: 1
})
console.log(this.clicks);
if (this.clicks.length > 1) {
this.compare(click.name);
}
}
compare = (name) => {
if (name === this.clicks[0]) {
console.log('pair')
} else {
console.log('nope');
}
}
render() {
return (
<div className="App">
<Header />
<div className='Grid-container'>
<div className='wrapper'>
{this.cards.map(child =>
<Grid click={() => this.clickHandler(child)}
active={this.state.current === 0}
id={child.id}
/>)}
</div>
</div>
<Footer />
</div>
);
}
}
export default App;
grid.js
import React from 'react';
import './grid.css';
const Grid = (props) => {
return (
<div className={'Child' + (props.active ? '': ' active')}
onClick={props.click}
>
{props.id}
</div>
);
}
export default Grid;
App.css
.Grid-container {
display: flex;
background-color: black;
justify-content: center;
align-items: center;
}
.wrapper {
display: grid;
width: 700px;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, 1fr);
grid-gap: 10px;
background-color: black;
justify-content: center;
align-items: center;
}
**grid.css**
.Child {
width: auto;
height: 120px;
background-color: azure;
border-radius: 10px;
}
.Child.active {
width: auto;
height: 120px;
background-color: red;
border-radius: 10px;
}

You have to use index for this. You have index as second argument in map,
{this.cards.map( ( child, index ) =>
<Grid
click={() => this.clickHandler(child,index)}
active={this.state.current === index}
id={child.id}
key ={child.id} //Provide unique key here
/>
)}
Your click hander should be,
clickHandler = (click,index) => {
this.clicks.push(click.name);
this.setState({
current: index //Set index in a state
})
console.log(this.clicks);
if (this.clicks.length > 1) {
this.compare(click.name);
}
}
You need to initialize current state as empty, otherwise you will get first grid by default active
state = {
current: '',
}
Your Grid component will be this,
const Grid = (props) => {
return (
<div
className={`Child ${props.active ? 'active': ''}`} //Your condition should be this
onClick={props.click}
>
{props.id}
</div>
);
}
Demo

Updating state section
state = {
current: -1, // nothing is selected it contains id of selected card
clicks: [],
}
clickHandler = (selectedId) => {
const { clicks, current } = this.state;
if (current === -1) { // no card selected to check
this.setState({
current: selectedId,
clicks: clicks.includes(selectedId) ? clicks : [...clicks, selectedId],
});
return; // no more in this funtion
}
if (selectedId === current) { // already selected card(totally same card)
this.setState({
current: -1, // unselect last selected card :(
clicks: clicks.slice(0, clicks.length - 1), // remove last selected click
});
} else { // different card. here check if they have same name
if (this.cards[selectedId].name === this.cards[current].name) {
// couple cards
console.log('Bingo They are Same!!!');
this.setState({
current: -1,
clicks: [...cliks, selectedId], // add just selected card in cliks
});
} else {
// Oh, you failed!
this.setState({
current: -1, // unselect last selected card :(
clicks: clicks.slice(0, clicks.length - 1),
});
}
}
}
Render
...
<Grid
click={() => this.clickHandler(child.id)}
active={this.state.clicks.includes(child.id)} // check if it is included in clicks
id={child.id}
/>
...
Then you can see cool cards game. :)

Related

How Can I change the icon made with prop over transform option in css?

I have a React accordion component with defined themes and icons.
I need to change the icon with on click via transform option in styled components and storybook.
Also upon clicking, the background color of the title div and content div needs to change.
I have defined color prop and icon, but not sure what is next?
Here is my React component:
import { string, node, oneOf, bool } from "prop-types"
import * as Styled from "./Accordion.styled"
import Icon from "design-system/components/icon"
import React, { useState } from 'react'
const Accordion = ({ children, icon, text, button,
color, activeColor, }) => {
const [isActive, setIsActive] = useState(false);
return (
<Styled.Accordion
color={color}
>
<Styled.Title onClick={() => setIsActive(!isActive)}
color={isActive ? activeColor : color}
> {text}
<Styled.Icon color={color}>
<Icon name={icon}/>
</Styled.Icon>
</Styled.Title>
{isActive &&
<Styled.Content
color={isActive ? activeColor : color} >
{children}
{button}
</Styled.Content>
}
</Styled.Accordion>
);
}
Accordion.propTypes = {
text: string.isRequired,
children: node.isRequired,
icon: string,
name: string,
button: node,
color: oneOf(["primary", "neutrals", "grey"]),
activeColor: oneOf(["primary", "neutrals", "grey"]),
}
Accordion.defaultProps = {
children: null,
icon: null,
name: null,
button: null,
color: "",
activeColor: "",
}
export default Accordion
and here are the styles:
import styled from "#emotion/styled"
import { css } from "#emotion/react"
export const Accordion = styled.div`
display: flex;
text-decoration: none;
width: auto;
height: auto;
flex-direction: column;
align-items: flex-start;
justify-content: start;
border-radius: 30px;
`
export const Title = styled.div`
width: auto;
height: auto;
display: inline-flex;
gap: 161px;
border-radius: 10px 10px 0px 0px;
padding: 10px 0px 0px 10px;
color: ${({ color, theme }) => {
switch (color) {
case "grey":
return theme.colors.grey[600]
case "neutrals":
return theme.colors.neutrals[100]
case "primary":
return theme.colors.primary[500]
default:
return theme.colors.grey[600]
};
}};
background-color: ${({ color, theme }) => {
switch (color) {
case "grey":
return theme.colors.grey[600]
case "neutrals":
return theme.colors.neutrals[100]
case "primary":
return theme.colors.primary[500]
default:
return theme.colors.grey[600]
}
}};
`
export const Content = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: start;
width: auto;
height: auto;
border-radius: 0px 0px 10px 10px;
padding: 10px 100px 0px 10px;
background-color: ${({ color, theme }) => {
switch (color) {
case "grey":
return theme.colors.grey[600]
case "neutrals":
return theme.colors.neutrals[100]
case "primary":
return theme.colors.primary[500]
default:
return theme.colors.grey[600]
}
}};
color: ${({ color, theme }) => {
switch (color) {
case "grey":
return theme.colors.grey[600]
case "neutrals":
return theme.colors.neutrals[100]
case "primary":
return theme.colors.primary[500]
default:
return theme.colors.grey[600]
}
}};
`
export const Icon = styled.div`
display: flex;
align-items: flex-start;
width: auto;
height: auto;
`
I see you're already passing icon as a prop to this component. If you want to change the icon on click, you can create a state and set that as the initial value, then update it when you like.
const [icon, setIcon] = useState(props.icon);
(you can avoid using a prop as the initial value by lifting state up and passing an additional click handler down from the parent function)
and then you might want to create an onClick method for the icon div that sets this icon:
const changeIcon = () => {
// some logic to select an updated icon
setIcon(newIcon);
}
or you could just pass an icon to this method:
const changeIcon = newIcon => {
setIcon(newIcon);
}
and assign it to said div:
return (
...
<Styled.Icon onClick={changeIcon}>
<Icon name={icon} />
</Styled.Icon>
....
);
For doing anything when the "background" is clicked, you can assign a similar click listener to the Accordion component (or whichever component's background you want to have this behaviour on) and if you don't want anything to happen if an element on top of this background is clicked, you can stop this event propagation with event.stopPropagation(), where event is automatically passed to the click handler:
return (
<SomeElement onClick={event => event.stopPropagation()} />
);
I hope that answers your questions. Consider going over the short React tutorial that covers the basics of React, including when to use state.
change the icon with on click
Assuming you get an "active icon" from props, then just switch it depending on your isActive state:
const Accordion = ({
icon,
activeIcon,
}) => {
const [isActive, setIsActive] = useState(false);
return (
<Styled.Title onClick={() => setIsActive(!isActive)}>
<Styled.Icon>
{/* Provide the desired name prop value depending on state */}
<Icon name={isActive ? activeIcon : icon}/>
</Styled.Icon>
</Styled.Title>
);
};
the background color of the title di[v] and content div needs to change
...possibly:
via transform option in styled components
For this, you can indeed use styled-components adapting based on props:
const Accordion = ({
color,
activeColor,
}) => {
const [isActive, setIsActive] = useState(false);
return (
<Styled.Title
onClick={() => setIsActive(!isActive)}
color={isActive ? activeColor : color}
>
</Styled.Title>
);
};
// Accordion.styled
export const Title = styled.div`
// Adapt the bgColor value based on color prop
background-color: ${(props) => props.color};
`;

Update of multi-object state and input values on toggle not working

I have two components namely, 'SectionFruits' & 'ToggleFruit' and a state called 'selectedFruits' which is an array of objects like so:
useState([
{
name: 'Apple',
price: '',
quantity: '',
},
{
name: 'Mango',
price: '',
quantity: '',
},
])
'ToggleFruit' is a component which has the logic of add/remove fruit's properties (name, price, quantity) like so:
import React, { useState, createRef } from "react";
import styled from "styled-components";
const StyledToggleFruit = styled.div`
margin-bottom: 12px;
& > div {
display: flex;
align-items: center;
input[type="checkbox"] {
margin: 0;
}
h6 {
margin: 0;
cursor: pointer;
color: coral;
}
}
aside {
display: flex;
margin-top: 2px;
input {
margin-right: 8px;
}
}
`;
const ToggleFruit = ({
Id,
state,
setState,
ischecked,
label,
price,
quantity
}) => {
const toggleRef = createRef();
const [priceValue, setPriceValue] = useState(price);
const [quantityValue, setQuantityValue] = useState(quantity);
const [checkedState, setCheckedState] = useState(
ischecked ? ischecked : false
);
const handleToggle = (e) => {
setCheckedState(!checkedState);
if (state.some((object) => object.name.includes(label))) {
const removeFruitArr = state.filter((item) => item["name"] !== label);
setState(removeFruitArr);
} else {
setState((prevState) => [
...prevState,
{
name: label,
price: priceValue,
quantity: quantityValue
}
]);
}
};
return (
<StyledToggleFruit>
<div onClick={handleToggle}>
<div>
<input
ref={toggleRef}
onChange={handleToggle}
ischecked={ischecked && ischecked.toString()}
checked={checkedState}
type="checkbox"
/>
</div>
<h6>{label}</h6>
</div>
{checkedState && (
<aside>
<input
required
onChange={(e) => setPriceValue(e.target.value)}
value={priceValue}
type="text"
placeholder="Price"
/>
<input
required
onChange={(e) => setQuantityValue(e.target.value)}
value={quantityValue}
type="text"
placeholder="Quantity"
/>
</aside>
)}
</StyledToggleFruit>
);
};
export default ToggleFruit;
And 'SectionFruits' simply runs a map over FRUITS and renders them in the DOM. There are two issues I'm facing:
The toggle(add/remove) of fruits in the state is behaving weirdly
Price & Quantity are not being added to the state
Sandbox link: https://codesandbox.io/s/festive-haze-5ono9
I've edited a bit your code on codesandbox, please check it out:
https://codesandbox.io/s/upbeat-estrela-5yqbs?file=/src/components/ToggleFruit.js:660-676

React beautiful DND - I get "Unable to find draggable with id: task-2

In the following code, I have two columns. One column has all the tasks. The other column has a group of selected tasks. So, my expectation is that, when a user drags and drops a task from the total tasks column to the selected tasks column, the task should still be there in total tasks column. When the tasks are there in the selected tasks column, there will be a button visible to delete the tasks from selected tasks column. When I click this button, the task gets removed from the selected tasks column. But I am not able to drag it again from the total tasks column. I am getting the following error:
Unable to find draggable with id: task-2
Request you guys to take a look and tell me what's the mistake I am doing
Please find the link for the codesandbox:
https://codesandbox.io/s/unruffled-waterfall-wdyc5?file=/src/task.jsx
Also this is my code:
App.jsx:
import React from 'react';
import '#atlaskit/css-reset';
import styled from 'styled-components';
import { DragDropContext } from 'react-beautiful-dnd';
import initialData from './initial-data';
import Column from './column';
const Container = styled.div`
display: flex;
`;
class App extends React.Component {
state = initialData;
onDragStart = start => {
const homeIndex = this.state.columnOrder.indexOf(start.source.droppableId);
this.setState({
homeIndex,
});
};
onDragEnd = result => {
this.setState({
homeIndex: null,
});
const { destination, source, draggableId } = result;
if (!destination) {
return;
}
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
const home = this.state.columns[source.droppableId];
const foreign = this.state.columns[destination.droppableId];
if (home === foreign) {
const newTaskIds = Array.from(home.taskIds);
newTaskIds.splice(source.index, 1);
newTaskIds.splice(destination.index, 0, draggableId);
const newHome = {
...home,
taskIds: newTaskIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newHome.id]: newHome,
},
};
this.setState(newState);
return;
}
const foreignTaskIds = Array.from(foreign.taskIds);
foreignTaskIds.splice(destination.index, 0, draggableId);
const newForeign = {
...foreign,
taskIds: foreignTaskIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newForeign.id]: newForeign,
},
};
this.setState(newState);
};
deleteHandler = (taskId) => {
console.warn("I am going to delete: " + taskId);
var columnId = 'column-2';
const column = this.state.columns[columnId];
const columnTaskIds = Array.from(column.taskIds);
columnTaskIds.splice(columnTaskIds.indexOf(taskId), 1);
const newcolumn = {
...column,
taskIds: columnTaskIds,
};
var newState = null;
newState = {
...this.state,
columns: {
...this.state.columns,
[newcolumn.id]: newcolumn
}
};
this.setState(newState);
console.log(newState);
}
render() {
return (
<DragDropContext
onDragStart={this.onDragStart}
onDragEnd={this.onDragEnd}
>
<Container>
{this.state.columnOrder.map((columnId, index) => {
const column = this.state.columns[columnId];
const tasks = column.taskIds.map(
taskId => this.state.tasks[taskId],
);
const isDropDisabled = index < this.state.homeIndex;
return (
<Column
key={column.id}
column={column}
tasks={tasks}
isDropDisabled={isDropDisabled}
deleteHandler={this.deleteHandler}
/>
);
})}
</Container>
</DragDropContext>
);
}
}
export default App
column.jsx
import React from 'react';
import styled from 'styled-components';
import { Droppable } from 'react-beautiful-dnd';
import Task from './task';
const Container = styled.div`
margin: 8px;
border: 1px solid lightgrey;
border-radius: 2px;
width: 220px;
display: flex;
flex-direction: column;
`;
const Title = styled.h3`
padding: 8px;
`;
const TaskList = styled.div`
padding: 8px;
transition: background-color 0.2s ease;
background-color: ${props => (props.isDraggingOver ? 'skyblue' : 'white')};
flex-grow: 1;
min-height: 100px;
`;
export default class Column extends React.Component {
isSelectedTasksColumn = this.props.column.id === 'column-2';
render() {
return (
<Container>
<Title>{this.props.column.title}</Title>
<Droppable
droppableId={this.props.column.id}
isDropDisabled={this.props.isDropDisabled}
>
{(provided, snapshot) => (
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapshot.isDraggingOver}
>
{this.props.tasks.map((task, index) => (
<Task key={task.id} task={task} index={index} isSelectedTasksColumn={this.isSelectedTasksColumn} deleteHandler={this.props.deleteHandler}/>
))}
{provided.placeholder}
</TaskList>
)}
</Droppable>
</Container>
);
}
}
task.jsx
import React from 'react';
import styled from 'styled-components';
import { Draggable } from 'react-beautiful-dnd';
const Container = styled.div`
border: 1px solid lightgrey;
border-radius: 2px;
padding: 8px;
margin-bottom: 8px;
background-color: ${props =>
props.isDragDisabled
? 'lightgrey'
: props.isDragging
? 'lightgreen'
: 'white'};
`;
const DeleteButton = styled.button`
justify-self: center;
align-self: center;
border: 0;
background-color: black;
color: white;
padding: 7px;
cursor: pointer;
display: ${props => props.isSelectedTasksColumn ? "inline" : "none"};
float: right;
`;
const DisplayValue = styled.p`
align-self: center;
font-size: 20px;
padding: 0;
margin: 0;
display: inline;
`;
export default class Task extends React.Component {
render() {
const isDragDisabled = false;
return (
<Draggable
draggableId={this.props.task.id}
index={this.props.index}
isDragDisabled={isDragDisabled}
>
{(provided, snapshot) => (
<Container
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
isDragging={snapshot.isDragging}
isDragDisabled={isDragDisabled}
>
<DisplayValue>{this.props.task.content}</DisplayValue>
<DeleteButton isSelectedTasksColumn = {this.props.isSelectedTasksColumn}
onClick={() => this.props.deleteHandler(this.props.task.id)}>
✖
</DeleteButton>
</Container>
)}
</Draggable>
);
}
}
initial-data.js:
const initialData = {
tasks: {
'task-1': { id: 'task-1', content: 'Task 1' },
'task-2': { id: 'task-2', content: 'Task 2' },
'task-3': { id: 'task-3', content: 'Task 3' },
'task-4': { id: 'task-4', content: 'Task 4' },
},
columns: {
'column-1': {
id: 'column-1',
title: 'All tasks',
taskIds: ['task-1', 'task-2', 'task-3', 'task-4'],
},
'column-2': {
id: 'column-2',
title: 'Selected tasks',
taskIds: [],
}
},
// Facilitate reordering of the columns
columnOrder: ['column-1', 'column-2'],
};
export default initialData;
Removing React.StrictMode could help
https://reactjs.org/docs/strict-mode.html

How to make the visited step active?

I am making a simple react application and included react-stepper-horizontal library and things are fine.
Working Example:
Appropriate Code related to stepper:
const Form = () => {
.
.
.
const [currentPage, setCurrentPage] = useState(1);
const sections = [
{ title: 'Basic Details', onClick: () => setCurrentPage(1) },
{ title: 'Employment Details', onClick: () => setCurrentPage(2) },
{ title: 'Review', onClick: () => setCurrentPage(3) },
];
<Stepper
steps={sections}
activeStep={currentPage}
activeColor="red"
defaultBarColor="red"
completeColor="green"
completeBarColor="green"
/>
.
.
.
}
Steps to reproduce issue:
-> There are totally three steps 1,2,3 and each have different sections as Basic Details, Employment Details and Review respectively.
-> Now if user enter any of the input field in Step 1 and goes to Step 2 and fill some input fields there and goes to Step 3 to review it and if he comes back to the Step 1 again then the active state is lost in Step 3.
-> So now issue is if we want to go to step 3 then we need to again go three steps to reach last Step 3.
Requirement:
-> If user once visited any step then if he comes to any previous step then all the steps that he visited previously needs to be in active.
Eg:
-> If user landed in Step 1, then using next button , he reaches the Step 3 and if he wish to come back to Step 1 to modify some inputs and again if he wants to go to Step 3 for review step then it should be possible by clicking on the Step 3 because he already visited that step.
Kindly help me to achieve the result of making the steps in active state upto which the user visits.. If user visits Step 3 and goes back to step 1 on click of the Step 1 circle then there should be possibility to come back to Step 3 again as he already visited the Step 3..
Any solution without any library also welcomed.
This is a big issue if we have more steps (eg 7 steps). So please kindly help me.. A big thanks in advance..
Here's a simple implementation of the <Stepper /> component in question. The key is to have a tracker that tracks the visited steps internally, persist that information across re-renders.
Demoboard Playground
const { useState, useEffect, useMemo } = React;
const cx = classNames;
function range(a, b) {
return new Array(Math.abs(a - b) + 1).fill(a).map((v, i) => v + i);
}
function Stepper({ steps, activeStep, children }) {
const count = steps.length;
const listOfNum = useMemo(() => range(1, count), [count]);
const tracker = useMemo(() => {
let highestStep = 0;
function hasVisited(step) {
return highestStep >= step;
}
function addToBackLog(step) {
if (step > highestStep) highestStep = step;
}
return {
hasVisited,
addToBackLog,
getHighestStep() {
return highestStep;
},
};
}, []);
tracker.addToBackLog(activeStep);
const noop = () => {};
const prevStep = steps[activeStep - 2];
const currentStep = steps[activeStep - 1];
const nextStep = steps[activeStep];
return (
<div>
<div>
{" "}
{listOfNum.map((num, i) => {
const isActive = activeStep == num;
const isVisited = tracker.hasVisited(num);
const isClickable = num <= tracker.getHighestStep() + 1 || isVisited;
return (
<div
key={num}
className={cx("circle", {
active: isActive,
visited: isVisited,
clickable: isClickable,
})}
onClick={isClickable ? steps[i].onClick : noop}
>
{num}{" "}
</div>
);
})}{" "}
</div>{" "}
<h2> {currentStep && currentStep.title} </h2> <div> {children} </div>{" "}
<div className="footer">
{" "}
{prevStep ? (
<button onClick={prevStep.onClick}> prev </button>
) : null}{" "}
{nextStep ? <button onClick={nextStep.onClick}> next </button> : null}{" "}
</div>{" "}
</div>
);
}
function App() {
const [currentPage, setCurrentPage] = useState(1);
const sections = [
{
title: "Un",
onClick: () => setCurrentPage(1),
},
{
title: "Deux",
onClick: () => setCurrentPage(2),
},
{
title: "Trois",
onClick: () => setCurrentPage(3),
},
{
title: "Quatre",
onClick: () => setCurrentPage(4),
},
{
title: "Cinq",
onClick: () => setCurrentPage(5),
},
];
return (
<Stepper steps={sections} activeStep={currentPage}>
I 'm page {currentPage}{" "}
</Stepper>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
body {
color: #0f0035;
padding-bottom: 2rem;
}
.circle {
display: inline-flex;
height: 2em;
width: 2em;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: lightgrey;
margin: 0 0.5em;
color: white;
cursor: not-allowed;
}
.active {
background-color: rgba(50, 50, 250) !important;
}
.visited {
background-color: rgba(50, 50, 250, 0.5);
}
.clickable {
cursor: pointer;
}
.footer {
margin-top: 1em;
display: flex;
justify-content: space-between;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.6/index.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React replace item in array

I am trying to replace a single item in my array with another item, I have a placeholder with a number position on it, and when I click on add the first item should go into the 1st position , 2nd item into the 2nd position and so on.
At the moment it will add the item in all positions when clicked on, which is not the behaviour I would like.
If I remove the number position placeholder I can get them to go into the array in the correct position but I'm unable to get it to work with the placeholder.
How can I get the product item when clicked on replace the number position in the array?
https://codesandbox.io/s/6z48qx8vmz
Hello.js
import React from 'react';
import update from 'immutability-helper'
import styled from 'styled-components'
import Product from './Product'
const NumberWrap = styled.div`
display: flex;
flex-wrap:wrap
border: 1px solid #ccc;
flex-direction: row;
`
const Numbers = styled.div`
display:flex;
background: #fafafa;
color: #808080;
font-size: 32px;
flex: 0 0 20%;
min-height: 200px;
justify-content: center;
align-items:center;
border-right: 1px solid #ccc;
`
const CardWrap = styled.div`
display:flex;
flex-wrap: wrap;
flex-direction: row;
margin-top: 20px;
`
export default class Hello extends React.Component {
constructor() {
super()
this.state = {
placeholder: [1,2,3,4,5],
data: [
{ id: 1, header: 'Item 1'},
{ id: 2, header: 'Item 2'},
{ id: 3, header: 'Item 3'},
{ id: 4, header: 'Item 4'}
],
addedItems: [],
}
this.handleAdd.bind(this)
this.handleRemove.bind(this)
}
handleAdd(id) {
this.setState({
addedItems: update(this.state.addedItems, {
$push: [
id,
],
})
})
}
handleRemove(index) {
this.setState({
addedItems: update(this.state.addedItems, {
$splice: [
[index, 1]
],
})
})
}
render() {
return(
<div>
<NumberWrap>
{
this.state.placeholder.map(item =>
<Numbers>{item}
{
this.state.data.filter(item =>
this.state.addedItems.indexOf(item.id) !== -1).slice(0, 5).map(item =>
<Product {...item} remove={this.handleRemove} />
)
}
</Numbers>
)}
</NumberWrap>
<CardWrap>
{
this.state.data.map(item =>
<Product {...item} add={()=>this.handleAdd(item.id)} />
)}
</CardWrap>
</div>
)
}
}
Product.js
import React from "react";
import styled from "styled-components";
const Card = styled.div`
flex: 0 0 20%;
border: 1px solid #ccc;
`;
const Header = styled.div`
padding: 20px;
`;
const AddBtn = styled.button`
width:100%;
height: 45px;
`;
const Product = props => {
const { add, id, header, remove } = props;
return (
<Card>
<Header key={id}>
{header}
</Header>
<AddBtn onClick={add}>Add</AddBtn>
<AddBtn onClick={remove}>Remove</AddBtn>
</Card>
);
};
export default Product;
I had bit hard time understanding the question but is this what you want https://codesandbox.io/s/vj7vxw198y ? It still would need some work to allow adding same item multiple times if you want that.

Categories

Resources