I am using React. I am trying to dynamically add and remove input fields. I am trying to make a list of objects like
[
{"item": 1, "key": "RAM", "value": "8GB"},
{"item": 1, "key": "Color", "value": "Black"},
{"item": 1, "key": "Used For", "value": "6 months"},
]
There can be any number of objects in the above list. I want to send a post request with the above data. The problem I am facing is creating the above pattern. I want to create an Add button which will show two input fields for the key and value side by side. Then I want to add them to the above list when the user enters the values. Don't worry about the item because it is always the same.Here is a CodeSandBox link. Please expand the browser tab in CodeSandBox to see the full page.
I have done the following:
App.js
import React, { useState } from "react";
import {
SpecificationFormContainer,
SpecificationFormCard,
SpecificationFormTitle,
SpecificationInputRow,
SpecificationValue,
AddMoreButton,
RemoveButton
} from "./Elements";
import "./styles.css";
export default function App() {
const [numberOfSpecFields, setNumberOfSpecFields] = useState(0);
const [specificationKeys, setSpecificationKeys] = useState("");
const [specificationValues, setSpecificationValues] = useState("");
const [specifications, setSpecifications] = useState([]);
const handleSpecKeyChange = (event) => {
setSpecificationKeys(event.target.value);
};
const handleSpecValueChange = (event) => {
setSpecificationValues(event.target.value);
};
const handleFocusOut = (event, index) => {
console.log(event);
if (event.target.value === "") {
console.log("String Empty");
return;
}
};
return (
<SpecificationFormContainer>
<SpecificationFormCard>
<SpecificationFormTitle>Add Specifications</SpecificationFormTitle>
<form>
{[...Array(numberOfSpecFields)].map((e, index) => {
return (
<SpecificationInputRow key={index}>
<SpecificationValue
placeholder="Key"
onChange={handleSpecKeyChange}
name="key"
onBlur={() => {
handleFocusOut(index);
}}
/>
<SpecificationValue
placeholder="Value"
onChange={handleSpecValueChange}
name="value"
onBlur={() => {
handleFocusOut(index);
}}
/>
</SpecificationInputRow>
);
})}
</form>
<AddMoreButton
onClick={() => {
setNumberOfSpecFields((prev) => prev + 1);
}}
>
Add
</AddMoreButton>
{numberOfSpecFields > 0 && (
<RemoveButton
onClick={() => {
setNumberOfSpecFields((prev) => prev - 1);
}}
>
Remove
</RemoveButton>
)}
</SpecificationFormCard>
</SpecificationFormContainer>
);
}
Elements.js
import styled from "styled-components";
export const SpecificationFormContainer = styled.div`
width: 100%;
border: 2px solid blue;
padding: 1em;
display: flex;
justify-content: center;
align-items: center;
`;
export const SpecificationFormCard = styled.div`
background-color: rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
/* min-height: 15em; */
min-width: 50em;
`;
export const SpecificationFormTitle = styled.div`
width: 100%;
font-size: large;
text-align: center;
`;
export const SpecificationInputRow = styled.div`
width: 100%;
display: flex;
justify-content: center;
`;
export const SpecificationValue = styled.input`
width: 40%;
padding: 0.5em;
margin: 0.5em;
`;
export const AddMoreButton = styled.button`
width: 7em;
`;
export const RemoveButton = styled.button`
width: 7em;
`;
In the handleFocusOut function, the event is 0. I was thinking of making an object with the key value pair in the handleFocusOut function based on the index as the key and value input pair have the same index.
I want to know why this does not work and how to make it work. There is most likely a better way to do this as this approach probably is not the best since I am only just a beginner. So, if there is a better way to achieve this, I want to know that.
In the handleFocusOut function, the event is 0.
It's 0 because you are passing index as an argument and ignoring the event. You could use a curried function to accept the event and index as well.
const handleFocusOut = (index) => (event) => {
// do stuff
};
onBlur={handleFocusOut(index)}
or add a function directly in JSX:
onBlur={(event) => handleFocusOut(event, index)}
Related
In this project, React, TypeScript and ant design are used. In a part of the project, only one box out of three boxes should be selected. I have used "useState" and toggle, but when I select one, they are all selected together when only one should be selected. I am using React version 18.2.0
I would be grateful if you could guide me.
allBox{
display: flex;
align-items: center;
justify-content: space-between;
width: 700px
}
.box{
width: 34px;
height: 34px;
border: 3px solid yellow;
background: green;
color:blue;
}
.box.active{
border: 3px solid black;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import React, { useState } from "react";
function MyBox() {
const boxes = [
{
id: 1,
type: "1",
},
{
id: 2,
type: "2",
},
{
id: 3,
type: "3",
},
];
const [boxSelect, setBoxSelect] = useState(false);
const handleSelect = () => {
setBoxSelect(!boxSelect);
};
return (
<div>
<div className='allBox'>
{boxes.map((box) => {
return (
<div className={`${box} ${boxSelect && 'active'}`} key={box.id} onClick={handleSelect}>
<p>{box.type}</p>
</div>
);
})}
</div>
</div>
);
}
export default MyBox;
It's because all your boxes share the same const [boxSelect, setBoxSelect] = useState(false); so if you click on one of them, they will all be selected.
You have two solutions:
create a children component with only:
function mySubBox(box) {
const [boxSelect, setBoxSelect] = useState(false);
const handleSelect = () => {
setBoxSelect(!boxSelect);
};
return (
<div className={`${box} ${boxSelect && 'active'}`} key={box.id} onClick={handleSelect}>
<p>{box.type}</p>
</div>
);
}
And the parent loops through it:
{boxes.map((box, i) => <mySubBox box={box} key={i}/>)}
another solution would be to keep only one component and store the selected boxes in an array: if I click on the 2nd box, my selected array is [2], and you add/remove the item inside the array.
Working on an e-commerce store project where I have a Slider component.
I am fetching my own JSON data from a localhost server & mapping over the array for each slide.
My goal is to find a way to use the same useState() to render the items onto the page & click a button to move to the next slide.
I have tried to do something like useState([], 0)
One for my array & another one to change index on button click however this did not work lol...
The array is of course for the data to be displayed however the tricky part for me is figuring out a way to move to the next page.
I am trying to use transform:translateX in my Wrapper styled component and attempting to pass props in so I can change the slide to the next slide and still render the data on the page.
How can I go about using state in this way described above based on my code?
*** Before reading the code snippets, my current code shows that I have tried creating two states, and passing the second state with my integer into my fetch request, no errors pop up but it doesn't work obviously as it doesn't have an array of items to index through.
In the code snippet I have included the code for the entire slider & also the information inside my data.json file.
import {useState, useEffect} from 'react';
import { ArrowLeftOutlined, ArrowRightOutlined } from "#material-ui/icons";
import styled from "styled-components";
const Container = styled.div`
width: 100%;
height: 95vh;
display: flex;
// background-color: #b3f0ff;
position: relative;
overflow: hidden;s
`;
const Arrow = styled.div`
width: 50px;
height: 50px;
background-color: #e6ffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
bottom: 0;
left: ${props => props.direction === "left" && "10px"};
right: ${props => props.direction === "right" && "10px"};
margin: auto;
cursor: pointer;
opacity: 0.5;
z-index: 2;
`;
const Wrapper = styled.div`
height: 100%;
display: flex;
transform: translateX({props => props.arrowIndx * -100}vw);
`
const Slide = styled.div`
width: 100vw;
height: 100vw;
display: flex;
align-items: center;
background-color: ${props => props.bg};
`
const ImgContainer = styled.div`
height: 100%;
flex:1;
`
const Image = styled.img`
padding-left: 30px;
align-items: left;
`
const InfoContainer = styled.div`
height: 80%;
flex:1;
padding: 30px;
`
const Title = styled.h1`
font-size: 50px
`
const Desc = styled.p`
margin: 50px 0px;
font-size: 20px;
font-weight: 500;
letter-spacing: 3px;
`
const Button = styled.button`
padding: 10px;
font-size: 20px;
background-color: transparent;
cursor: pointer;
`
const Slider = () => {
const [slideIndx, setSlideIndx] = useState([]);
const [arrowIndx, setArrowIndx] = useState(0);
const handleClick = (direction) => {
if(direction === "left"){
setArrowIndx(arrowIndx > 0 ? arrowIndx - 1 : 2)
} else{
setArrowIndx(arrowIndx < 2 ? arrowIndx + 1 : 0)
}
}
const fetchSliderItems = () => {
fetch('http://localhost:3000/sliderItems')
.then(resp => resp.json())
.then(data => {
console.log(data)
setArrowIndx(data)
setSlideIndx(data)
})
}
useEffect(() => {fetchSliderItems()}, [])
return (
<Container>
<Arrow direction="left" onClick={() => handleClick("left")}>
<ArrowLeftOutlined />
</Arrow>
<Wrapper arrowIndx={arrowIndx}>
{slideIndx.map((item) => (
<Slide bg={item.bg}>
<ImgContainer>
<Image src={item.img}/>
</ImgContainer>
<InfoContainer>
<Title>{item.title}</Title>
<Desc>{item.desc}</Desc>
<Button>SHOP NOW</Button>
</InfoContainer>
</Slide>
))}
</Wrapper>
<Arrow direction="right" onClick={() => handleClick("right")}>
<ArrowRightOutlined />
</Arrow>
</Container>
)
}
export default Slider
{
"sliderItems": [
{
"id": 1,
"img": "../images/model1.png",
"title": "SPRING CLEANING",
"desc": "DONT MISS OUR BEST COLLECTION YET! USE #FLATIRON10 TO RECEIVE 10% OFF YOUR FIRST ORDER",
"bg": "#b3ecff"
},
{
"id": 2,
"img": "../images/model2.png",
"title": "SHOW OFF HOW YOU DRESS",
"desc": "WITH OUR HUGE SELECTION OF CLOTHES WE FIT ALL YOUR STYLING NEEDS",
"bg": "#ccf2ff"
},
{
"id": 3,
"img": "../images/model3.png",
"title": "POPULAR DEALS",
"desc": "RECEIVE FREE SHIPPING ON ALL ORDERS OVER $50!",
"bg": "#fe6f9ff"
}
]
}
if you want to use 2 data at 1 use state you have 2 way to achieve that
store data as cell of array like below.
const [data,setData] = useState([1,2])
console.log(data[1])
set data as Object in useState
const [data,setData] = useState({data1: 1, data2: 2 })
but i suggest you to use second approach because it's easy to use.
one point you must have care about it in this approach is, if you want to update state with object-state you have to update state with deep copy and then react can re-render component
for example if you want to update data2 in object of state you have to dod this
const [data,setData] = useState({data1: 1, data2: 2 })
setData((prevState) => ({
...prevState,
data2: 'value',
}));
white this snippet you will get update data2 and react get re-render
in your case if you want save array and update it and if you want get re-render when pass new array you have to clone new array and pass it into useState and you can archive this with spread operator in JavaScript.
Example:
setData((prevState) => ({
...prevState,
data2: [...newArray],
}));
if you pass newArray instead of [...newArray] you just pass the reference of memory if array into state and if after setState you change newAereay at the rest of code your state will update to and you don't want this.
EDIT*** THE CODE IN THE SNIPPET IS NOT MEANT TO BE RUN BUT ONLY DISPLAY MY CODE, SORRY FOR THE CONFUSION
I am working a slider for an e-commerce app. Making each slider map over my useState array to create the different cards and the useState is receiving data from my fetched JSON server.
Now I am stuck at a brick wall in figuring how to use the same state to handle rendering the image onto my slider & click on the arrow to show the new slide.
I realize that I should update the state but I feel that I am over (or under) thinking things on how to do so.
My code snippets will show one, my code & two my JSON server data.
I receive an "uncaught error: too many re-renders." React limits number of renders to prevent an infinite loop
Can somebody please walk me through what I need to do here to complete this task? Thanks!
import {useState, useEffect} from 'react';
import { ArrowLeftOutlined, ArrowRightOutlined } from "#material-ui/icons";
import styled from "styled-components";
import { set } from 'react-hook-form';
const Container = styled.div`
width: 100%;
height: 95vh;
display: flex;
// background-color: #b3f0ff;
position: relative;
overflow: hidden;
`;
const Arrow = styled.div`
width: 50px;
height: 50px;
background-color: #e6ffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
bottom: 0;
left: ${(props) => props.direction === "left" && "10px"};
right: ${(props) => props.direction === "right" && "10px"};
margin: auto;
cursor: pointer;
opacity: 0.5;
z-index: 2;
`;
const Wrapper = styled.div`
height: 100%;
display: flex;
transform: translateX(${props => props.slideIndx.data2 * -100}vw);
`
const Slide = styled.div`
width: 100vw;
height: 100vw;
display: flex;
align-items: center;
background-color: ${props => props.bg};
`
const ImgContainer = styled.div`
height: 100%;
flex:1;
`
const Image = styled.img`
padding-left: 30px;
align-items: left;
`
const InfoContainer = styled.div`
height: 80%;
flex:1;
padding: 30px;
`
const Title = styled.h1`
font-size: 50px
`
const Desc = styled.p`
margin: 50px 0px;
font-size: 20px;
font-weight: 500;
letter-spacing: 3px;
`
const Button = styled.button`
padding: 10px;
font-size: 20px;
background-color: transparent;
cursor: pointer;
`
const Slider = () => {
const [slideIndx, setSlideIndx] = useState({data1:[], data2: 0});
const handleClick = (direction) => {
if(direction === "left"){
setSlideIndx(slideIndx.data2 > 0 ? slideIndx.data2 - 1 : 2);
} else{
setArrowIndx(slideIndx.data2 < 2 ? slideIndx.data2 + 1 : 0);
}
};
const newArray = []
setSlideIndx((slideIndx) => ({
...slideIndx,
data2: [...newArray]
}));
const fetchSliderItems = (id) => {
fetch('http://localhost:3000/sliderItems')
.then(resp => resp.json())
.then(data => {
console.log(data)
setSlideIndx(data)
})
}
useEffect(() => {fetchSliderItems()}, [])
return (
<Container>
<Arrow direction="left" onClick={() => handleClick("left")}>
<ArrowLeftOutlined />
</Arrow>
<Wrapper slideIndx={slideIndx.data2}>
{slideIndx.data1.map((item) => (
<Slide bg={item.bg}>
<ImgContainer>
<Image src={item.img}/>
</ImgContainer>
<InfoContainer>
<Title>{item.title}</Title>
<Desc>{item.desc}</Desc>
<Button>SHOP NOW</Button>
</InfoContainer>
</Slide>
))}
</Wrapper>
<Arrow direction="right" onClick={() => handleClick("right")}>
<ArrowRightOutlined />
</Arrow>
</Container>
)
}
export default Slider
{
"sliderItems": [
{
"id": 1,
"img": "../images/model1.png",
"title": "SPRING CLEANING",
"desc": "DONT MISS OUR BEST COLLECTION YET! USE #FLATIRON10 TO RECEIVE 10% OFF YOUR FIRST ORDER",
"bg": "#b3ecff"
},
{
"id": 2,
"img": "../images/model2.png",
"title": "SHOW OFF HOW YOU DRESS",
"desc": "WITH OUR HUGE SELECTION OF CLOTHES WE FIT ALL YOUR STYLING NEEDS",
"bg": "#ccf2ff"
},
{
"id": 3,
"img": "../images/model3.png",
"title": "POPULAR DEALS",
"desc": "RECEIVE FREE SHIPPING ON ALL ORDERS OVER $50!",
"bg": "#fe6f9ff"
}
]
}
It is totally possible to do this with one useState, two useStates and my personal preference, useReducer.
I'll just give an example. I haven't tried running this code, as I don't have the components. So treat it like pseudo code:
const initialState = {
selectedIndex: 0,
"sliderItems": [
{
"id": 1,
"img": "../images/model1.png",
"title": "SPRING CLEANING",
"desc": "DONT MISS OUR BEST COLLECTION YET! USE #FLATIRON10 TO RECEIVE 10% OFF YOUR FIRST ORDER",
"bg": "#b3ecff"
},
{
"id": 2,
"img": "../images/model2.png",
"title": "SHOW OFF HOW YOU DRESS",
"desc": "WITH OUR HUGE SELECTION OF CLOTHES WE FIT ALL YOUR STYLING NEEDS",
"bg": "#ccf2ff"
},
{
"id": 3,
"img": "../images/model3.png",
"title": "POPULAR DEALS",
"desc": "RECEIVE FREE SHIPPING ON ALL ORDERS OVER $50!",
"bg": "#fe6f9ff"
},
],
}
function reducer(state, action) {
switch (action.type) {
case 'setData':
return {
selectedIndex: 0,
sliderItems: action.sliderItems,
};
case 'slideRight':
return {
...state,
// this assumes you want to go back to index 0 if you slide right on last item
selectedIndex: state.sliderItems.length - state.selectedIndex > 1 ? state.selectedIndex + 1 : 0,
};
case 'slideLeft':
return {
...state,
// this assumes you want to go to last item if sliding left on first item
selectedIndex: state.selectedIndex === 0 ? state.sliderItems.length - 1 : state.selectedIndex - 1,
};
default:
return state;
}
}
const Example = () => {
const [{ sliderItems, selectedIndex }, dispatch] = useReducer(initialState, reducer);
return (
<Container>
<Arrow direction="left" onClick={() => dispatch({ type: 'slideLeft' })}>
<ArrowLeftOutlined />
</Arrow>
<Wrapper slideIndx={selectedIndex}>
{sliderItems.map((item) => (
<Slide bg={item.bg}>
<ImgContainer>
<Image src={item.img}/>
</ImgContainer>
<InfoContainer>
<Title>{item.title}</Title>
<Desc>{item.desc}</Desc>
<Button>SHOP NOW</Button>
</InfoContainer>
</Slide>
))}
</Wrapper>
<Arrow direction="right" onClick={() => dispatch({ type: 'slideRight' })}>
<ArrowRightOutlined />
</Arrow>
</Container>
)
}
If you choose to try this approach, just dispatch a setData action in your fetch handler. That will reset the state with new items and start at index 0. The behavior can of course be modified to your liking.
dispatch({
type: 'setData',
sliderItems: fetchResult.sliderItems,
})
I am experiencing some issues with radio group buttons. Can someone help me with this issue. (your help is much appreciated)
Current Issue: Radio Group are not clickable. Im not sure where I missed up!
Technologies Used: React hooks, styled-components , and design
for the radio buttons.
RadioGroup.js
import React from "react";
import { Radio } from "antd";
import styled from "styled-components";
import { theme } from "constants/theme";
const { royalBlue, black } = theme.colors;
const { medium } = theme.typography.size;
const { display } = theme.typography.font.family;
const StyledRadioGroup = styled(Radio.Group)`
width: 100%;
margin-top: 0.5rem;
text-align: left;
.ant-radio-wrapper {
justify-content: space-between;
padding: ${(props) => props.padding};
white-space: break-spaces;
margin-left: -1.5rem;
span.ant-radio + * {
font-family: ${display};
font-size: ${medium};
color: ${black};
letter-spacing: 0;
}
.ant-radio-inner {
border-color: ${royalBlue};
border-width: 0.2rem;
width: 2.5rem;
height: 2.5rem;
&::after {
background-color: ${royalBlue};
top: 0.2rem;
left: 0.2rem;
}
}
}
`;
const RadioGroup = ({
options,
onChange,
value,
defaultValue,
flex,
padding,
}) => {
return (
<StyledRadioGroup
size="large"
flex={flex}
padding={padding}
onChange={onChange}
value={value}
defaultValue={defaultValue}
>
{options.map((option, index) => (
<Radio value={option.stateKey} key={index}>
{option.value}
</Radio>
))}
</StyledRadioGroup>
);
};
export default RadioGroup;
Navigation.js
import React, { useState, useReducer } from "react";
import styled from "styled-components";
import RadioModal from "components/Feedback/RadioModal";
import RadioGroup from "components/Feedback/RadioGroup";
const renderRadioModal = () => {
const inputLabelsText = [
{
stateKey: "age",
label: "What is your age?",
},
];
const radioButtonOptions = [
{
stateKey: "covidImpact",
value: "I go to work/school normally",
},
{
stateKey: "covidImpact",
value: "I am healthy but in a stay-at-home quarantine",
},
{
stateKey: "covidImpact",
value: "I have mild symptoms but haven't been tested",
},
{
stateKey: "covidImpact",
value: "I am diagnosed with Covid-19",
},
];
const RadioGroupWithLabel = withLabel(() => (
<RadioGroup
onChange={true}
options={radioButtonOptions}
value={true}
padding="1rem 1rem"
size="large"
></RadioGroup>
));
return (
<RadioModal
maskClosable={true}
closable={true}
visible={radioModal}
onClose={() => closeRadioModal()}
transparent
>
<h2 className="title">We are almost done!</h2>
{inputLabelsText.map((label, index) => (
<>
<StyledInput
key={index}
label={label.label}
value={label.stateKey}
onChange={dispatchAction}
></StyledInput>
<RadioGroupWithLabel label={"How has COVID-19 impacted you?"} />
</>
))}
<FeedbackSubmitButton
title="Submit Feedback"
onClick={closeRadioModal}
></FeedbackSubmitButton>
</RadioModal>
);
};
You have onChange={true} and value={true}. You need to handle the onChange and value properly. The value prop needs to match the value in your options that you pass into the radio group.
The options you pass in should be in the structure [{ value, label }, { value, label }]
Then in your onChange you need to handle setting that value + have a place to store it
In older code of react it just display the text active or in active for an user. Now I want to replace it to red or green small dot how can I do it.
CSS
div .colored-circle {
display: inline-block;
margin-left: 5px;
margin-right: 5px;
margin-bottom: -2px;
border-radius: 50%;
border-style: solid;
border-width: 0.5px;
border-color: black;
height: 20px;
width: 20px;
}
Component:
const ColoredCircle = ({ color }) => {
const styles = { backgroundColor: color };
return color ? (
<Fragment>
<span className="colored-circle" style={styles} />
</Fragment>
) : null;
};
export default ColoredCircle;
Use the same logic you used to show 'active' or 'inactive' and instead of text add a div with css or img of the desired color.
If you happen to use material UI, you can do like this:
import React, { Fragment } from "react";
import { makeStyles } from "#material-ui/core";
const RADIUS_DOT = 1.5;
const useStyles = makeStyles((theme) => ({
circle: {
borderRadius: RADIUS_DOT,
height: RADIUS_DOT * 2,
width: RADIUS_DOT * 2,
padding: 0,
},
}));
const ColoredCircle = ({ color }) => {
const styles = { backgroundColor: color };
const classes = useStyles();
return color ? (
<Fragment>
<span className={classes.circle} style={styles} />
</Fragment>
) : null;
};
export default ColoredCircle;
you can use this package, can be helpful
npm i react-color-circle
import Circle from '#uiw/react-color-circle';
return (
<Circle
colors={[ '#F44E3B' ]}
/>);