How can I add item on localStroge through ReactJS - reactjs

I don't understand how I can add an item on localStroge with the handle change method in react js.
The problem is I want to add a favorite list. When I click the favorite list all checkbox fill I need to control them.
I want to store the filled item in my local storage. handlechange function fill all favorite icon why?
Every click will be a single item fill. Is it possible to Material UI or Martial UI checkBox icon? How can I handle it?
Here is my UI view
function Main() {
// Here is my state define
const [checked, setChecked] = React.useState(
localStorage.getItem("love") === "true"
);
const handleChange = (e) => {
localStorage.setItem("love", `${e.target.checked}`);
setChecked(e.target.checked);
console.log(e.target.checked);
};
return (
<>
<div className="row mt-5">
{isLoading ? (
<>
{Array.from({ length }).map((_, i) => (
<MainLoading key={i} />
))}
</>
) : error ? (
<p>Error occured</p>
) : (
<>
{data?.map((product) => (
<div className="col-md-3 mb-5 text-center" key={product.id}>
<img
className="w-100"
style={{ height: "200px", objectFit: "contain" }}
src={product.image}
alt=""
/>
<div>{product.title.substring(0, 20)}</div>
<button
onClick={() => handelAddTocard(product)}
className="mt-3"
>
Add to card
</button>
<button>
<Link
to={`/details/${product.id}`}
className="mt-3 text-decoration-none text-black"
>
view details
</Link>
</button>
{/* How can i control evey single item */}
<Checkbox
checked={checked}
onChange={handleChange}
icon={<FavoriteBorder />}
checkedIcon={<Favorite />}
/>
</div>
))}
</>
)}
</div>
</>
);
}
export default Main;

The problem is that you are using a boolean and you have no way to identify a specific item.
If you want to favorite multiple items, I would use something like this:
const [checked, setChecked] = React.useState(
JSON.parse(localStorage.getItem("loveIds") || "[]")
);
const handleCheck = (id, productChecked) => {
const newItems = productChecked ? [...checked, id] : checked.filter(x => x !== id);
localStorage.setItem("loveIds", JSON.stringify(newItemS));
setChecked(newItems);
console.log(newItems);
};
// ...
<Checkbox
checked={checked}
onChange={(e) => handleCheck(product.id, e.target.checked)}
icon={<FavoriteBorder />}
checkedIcon={<Favorite />}
/>

Related

Is there a way to populate an input field from a list of buttons in React?

I am building a React application and I need to populate an input field from a list of predefined buttons (the text of the btn should be displayed on the input if the user clicks on the btn).
For example: let's suppose the user sees 3 btns with the following text for each: 'Banana', 'Apple','Orange'.
If the user clicks on the Banana btn, the world 'Banana' must appear on the input field.
Here is the code of App.js :
function App() {
const [suggestiveListBorder, setSuggestiveListBorder] = useState("");
return (
<div className="App">
<Navbar />
<ListOfExercises
suggestiveListBorder={suggestiveListBorder}
></ListOfExercises>
<div className="pages">
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/chest"
element={
<ChestWorkouts
setSuggestiveListBorder={setSuggestiveListBorder}
/>
}
/>
<Route path="/hamstrings" element={<HamstringsWorkouts />} />
the code of ListOfExercises.js
export function ListOfExercises({ suggestiveListBorder }) {
const currentLocation = useLocation();
let currentLocat = currentLocation.pathname;
const chestExos = exercisesData.exercises.chest_Exercises;
return (
<>
{currentLocat === "/chest" ? (
<ChestExosList
suggestiveListBorder={suggestiveListBorder}
chestExos={chestExos}
/>
) : currentLocat === "/hamstrings" ? (
<span>hamstrings</span>
) : null}
</>
);
}
the code of ChestExosList.js
export function ChestExosList({ chestExos, suggestiveListBorder }) {
const chestValues = Object.values(chestExos);
return (
<div
className="chest-exos-list-container"
style={{
border: suggestiveListBorder,
}}
>
<div>
<p className="chest-exos-paragraph">
Have no idea about the exercise you want to do ? Here are some
suggestions :
</p>
</div>
<Stack direction={"row"} spacing={2} sx={{ marginBottom: "30px" }}>
{chestValues.map(
(elem, index) =>
index < 8 &&
index >= 0 && (
<Button type="secondary" key={index}>
{elem}
</Button>
)
)}
</Stack>
the code of Chest page
const Chest = ({ setSuggestiveListBorder }) => {
const { workouts, dispatch } = useWorkoutsContext();
useEffect(() => {
const fetchWorkouts = async () => {
const response = await fetch("/api/workouts");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_WORKOUTS", payload: json });
}
};
fetchWorkouts();
}, [dispatch]);
const antIcon = (
<LoadingOutlined
style={{
fontSize: 50,
}}
/>
);
return (
<div className="chest-page-container">
<div className="chest-page-workouts">
{!workouts && <Spin spinning={true} indicator={antIcon}></Spin>}
{workouts &&
workouts.map((workout) => (
<WorkoutDetails workout={workout} key={workout._id} />
))}
</div>
<WorkoutForm setSuggestiveListBorder={setSuggestiveListBorder} />
</div>
);
};
and a snippet of the code of WorkoutForm.js :
return (
<form className="chest-workouts-form" onSubmit={handleSubmit}>
<div className="chest-workouts-form-inner">
<h3>Add a New Workout</h3>
<label>Excercise Title : </label>
<input
onMouseOver={() => {
setSuggestiveListBorder("1.5px solid #1aac83");
}}
type="text"
onChange={(e) => setTitle(e.target.value)}
value={title}
// value=""
className={emptyFields.includes("title") ? "form-msg-error" : ""}
/>
...
thank you all.
Yes, you can. You can add a different onClick event on each button (see first button in example) or a more generic one if you're planning on having many buttons (second button in example)
const Component = () => {
const [inputValue, setInputValue] = useState("");
const firstButtonText = "First button";
return (
<>
<button onClick={() => setInputValue(firstButtonText)}>{firstButtonText}</button
<button onClick={(e) => setInputValue(e.target.innerHTML)}>Second button</button>
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
</>
)
}
Create state for you input value :
const [inputValue,setInputValue]=useState('');
Give this value to your input:
<input
onMouseOver={() => {
setSuggestiveListBorder("1.5px solid #1aac83");
}}
type="text"
onChange={(e) => setTitle(e.target.value)}
value={inputValue}
className={emptyFields.includes("title") ? "form-msg-error" : ""}
/>
On your button click, you set this value:
<button onClick={()=> setInputValue("button one"}>...
Do the same for your other buttons

React can't perform state update on unmounted component - checking isMounted not fixing

I am using axios to return data from an API and trying to present this in to various nested components in my React App.
The code looks something like this:
const Building = () => {
const { bid } = useParams();
const { userAccessToken } = useAuth();
const [buildingData, setBuildingData] = useState([]);
const bearerToken = `Bearer ${userAccessToken}`;
React.useEffect(() => {
let isMounted = true;
const axiosConfig = {
headers: { Authorization: bearerToken },
};
axios
.get(
"http://localhost:3001/building?requestedlid=2&requestedbid=" + bid,
axiosConfig
)
.then(function (response) {
if (isMounted) {
setBuildingData(response.data[0]);
}
})
.catch(function (error) {
// handle error
console.log(error);
});
return () => {
isMounted = false;
};
}, [bearerToken, bid]);
return (
<React.Fragment>
<Helmet title="Building Profile" />
<Container fluid className="p-0">
<Breadcrumb className="float-end mt-2">
<Breadcrumb.Item href="/dashboard/default">Home</Breadcrumb.Item>
<Breadcrumb.Item href="/buildings/portfolio">
Portfolio
</Breadcrumb.Item>
<Breadcrumb.Item active>Building Profile</Breadcrumb.Item>
</Breadcrumb>
<h1 className="h3 mb-3">
Building Profile
<OffcanvasHelp
id="buildingprofile"
name="Building Profile"
scroll
backdrop
/>
</h1>
<div className="clearfix"></div>
<Row>
<Col xl="8">
<BuildingProfile
name={buildingData.building_name}
status={buildingData.status}
description={buildingData.description}
keycontacts={buildingData.key_contacts}
created={buildingData.stats.created_date}
golive={buildingData.stats.golive_date}
/>
<Rooms />
</Col>
<Col xl="4">
<AccountManager />
<Map location={buildingData.location} />
<GetSupport type="commercial" />
</Col>
</Row>
</Container>
</React.Fragment>
);
};
My problem is I am receiving the common error:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Using the methods described in many StackOverflow answers, to check an isMounted boolean as in my code above, I am unable to resolve the issue.
Weirdly, the issue specifically occurs when I am passing these two props to the BuildingProfile component:
created={buildingData.stats.created_date}
golive={buildingData.stats.golive_date}
If I don't pass these two props, everything else works fine.
I'm sure I'm missing something silly but after several hours of trying to figure it out I'm still stuck. Anybody who can provide a pointer or any tips, I would be really grateful.
Many thanks
--- Update - including the BuildingProfile component:
const BuildingProfile = ({
name,
status,
description,
created,
golive,
keycontacts,
}) => {
// Modal config for "Deactivate Building"
const initOpenModals = () => {
let modals = {};
colors.forEach((color, index) => {
modals = Object.assign({}, modals, { [index]: false });
});
console.log(modals);
return modals;
};
const [openModals, setOpenModals] = useState(() => initOpenModals());
const toggle = (index) => {
// Toggle selected element
setOpenModals((openModals) =>
Object.assign({}, openModals, { [index]: !openModals[index] })
);
};
const notyf = useContext(NotyfContext);
const [type] = useState("success");
const [duration] = useState("5000");
const [ripple] = useState(true);
const [dismissible] = useState(false);
const [positionX] = useState("right");
const [positionY] = useState("top");
const navigate = useNavigate();
return (
<Card>
<Card.Header className="mb-0 pb-0">
<Card.Title className="mb-0">
<IsAllowed to="edit:buildings">
<div className="card-actions float-end">
<Dropdown align="end">
<Dropdown.Toggle as="a" bsPrefix="-">
<MoreHorizontal />
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item onClick={() => navigate("/buildings/edit")}>
Edit Building
</Dropdown.Item>
<React.Fragment key="deactivateBuilding">
<Dropdown.Item onClick={() => toggle("deactivateBuilding")}>
Deactivate Building
</Dropdown.Item>
<Modal
show={openModals["deactivateBuilding"]}
onHide={() => toggle("deactivateBuilding")}
centered
>
<Modal.Header closeButton>
<b>Admin Function:</b> Deactivate Building
</Modal.Header>
<Modal.Body className="m-3">
<p className="text-left mb-0">
Are you sure you want to deactivate the
<b>Bus Works</b> building? This will prevent the
building from showing up in the platform completely.
</p>
</Modal.Body>
<Modal.Footer>
<Button
variant="secondary"
onClick={() => toggle("deactivateBuilding")}
>
Close
</Button>{" "}
<Button
variant="danger"
onClick={() => {
toggle("deactivateBuilding");
notyf.open({
type,
message: "The building has been deactivated.",
duration,
ripple,
dismissible,
position: {
x: positionX,
y: positionY,
},
});
}}
>
Deactivate Building
</Button>
</Modal.Footer>
</Modal>
</React.Fragment>
</Dropdown.Menu>
</Dropdown>
</div>
</IsAllowed>
<h1 className="mb-0 pb-0">{name}</h1>
<Badge
className={
status === "Live Building"
? "my-2 btn-gradient inline"
: "my-2 inline"
}
bg="success"
>
{status}
</Badge>
</Card.Title>
</Card.Header>
<Card.Body>
<h5>Building Overview:</h5>
<p className="mb-4">{description}</p>
<div className="row">
<div className="col-md-4">
<div className="mb-4">
<h5>Created Date</h5>
<p>{created}</p>
</div>
</div>
<div className="col-md-4">
<div className="mb-4">
<h5>Go-Live Date</h5>
<p>{golive}</p>
</div>
</div>
<div className="col-md-4">
<div className="mb-4">
<h5>Key Contacts</h5>
<div>
<span className="me-1">
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="tooltip-right">Joe Bloggs</Tooltip>
}
>
<img
src={avatar3}
width="28"
height="28"
className="rounded-circle me-2"
alt="Joe Bloggs"
/>
</OverlayTrigger>
</span>
<span className="me-1">
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="tooltip-right">Joe Bloggs</Tooltip>
}
>
<img
src={avatar2}
width="28"
height="28"
className="rounded-circle me-2"
alt="Joe Bloggs"
/>
</OverlayTrigger>
</span>
<span className="me-1">
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="tooltip-right">Joe Bloggs</Tooltip>
}
>
<img
src={avatar1}
width="28"
height="28"
className="rounded-circle me-2"
alt="Joe Bloggs"
/>
</OverlayTrigger>
</span>
<Link to="#" className="d-inline-block text-muted fw-bold ms-2">
+2 more
</Link>
</div>
</div>
</div>
</div>
</Card.Body>
</Card>
);
};
I figured this out and wanted to share my answer in-case it helps anyone else.
It turns out that because I am trying to access the nested object property in the rendered component before the API call has finished, the property ("stats") doesn't exist. When the state eventually updates once the API call has finished, it cannot update the component resulting in the error I was seeing.
The way to fix this is to do something like this:
.... // useState should be set to an object, not an array
const [buildingData, setBuildingData] = useState({});
.... // skipping past intermediary code for brevity
.then(function (response) {
if (isMounted) {
setBuildingData({
name: response.data[0].building_name,
status: response.data[0].status,
description: response.data[0].description,
keycontacts: response.data[0].key_contacts,
created: response.data[0].stats.created_date,
golive: response.data[0].stats.golive_date
});
}
})
.... // then to access it in the component use:
<BuildingProfile
name={buildingData.name}
status={buildingData.status}
description={buildingData.description}
keycontacts={buildingData.keycontacts}
created={buildingData.created}
golive={buildingData.golive}
/>
Doing it this way means that the nested object is updated with API data inside of the useEffect hook, not in the component itself.
Hope this helps somebody.

Using useRef to assign focus to an element that conditionally exists

I am having some trouble trying to implement this functionality which creates an input field component upon clicking a button, and then assigning focus to the input field as well. I am getting an error saying that inputRef.current is undefined, and am not sure how to proceed.
export default function Post(props) {
const inputRef = React.useRef();
const [commentsToggle, setCommentsToggle] = React.useState(false);
function commentClickHandler() {
setCommentsToggle((prev) => !prev);
inputRef.current.focus();
}
return (
<div className="post">
<div className="postAuthor">
<Avatar
{...stringAvatar(`${props.post.author.username}`)}
alt={`${props.post.author.username}'s Avatar`}
src="./placeholder.jpg"
variant="rounded"
style={avatarStyle}
>
{props.post.author.username[0].toUpperCase()}
</Avatar>
<b>{props.post.author.username}</b> posted:
</div>
<p className="postContent">{props.post.content}</p>
<p className="postDate">{props.post.formatted_date}</p>
<span className="postButtonContainer">
<IconButton className="starsButton" onClick={starClickHandler}>
{props.post.stars.includes(userInfo.userID) ? (
<StarIcon />
) : (
<StarBorderIcon />
)}
{props.post.stars.length}
</IconButton>
<Tooltip title="Add a comment." placement="right">
<IconButton className="commentsButton" onClick={commentClickHandler}>
{commentsToggle ? <ChatBubbleIcon /> : <ChatBubbleOutlineIcon />}
{props.post.comments.length}
</IconButton>
</Tooltip>
</span>
<hr></hr>
<div>
<CommentList comments={props.post.comments}></CommentList>
{commentsToggle ? (
<NewCommentInput
ref={inputRef}
targetPostURL={props.post.url}
getUserData={props.getUserData}
setCommentsToggle={setCommentsToggle}
></NewCommentInput>
) : null}
</div>
</div>
);
}
The above is the code from my parent component, which will conditionally render the child component (the input) that I want to receive focus. Below is this input component:
const NewCommentInput = React.forwardRef((props, ref) => {
return (
<form className="commentInput" onSubmit={commentSubmitHandler}>
<TextField
ref={ref}
multiline
fullWidth
size="small"
name="comment"
placeholder="comment on this post..."
value={commentState.content}
onChange={commentChangeHandler}
></TextField>
<Button variant="outlined" onClick={commentSubmitHandler}>
Comment
</Button>
</form>
);
});
How can I solve inputRef.current being undefined when I click the button that create the component that has the ref?

Passing click function from One component to other component

In React material ui i am having two components where i am calling save function on button click, is it right way or not can anyone suggest the better way:
const callback = {};
return (
<>
{!state?.creditCard?.isSaved ? (
<Paper elevation={4} className={classes.paymentContainer}>
<Box className={classes.subPaymentContainer}>
<Typography className={classes.title}>Card Payment</Typography>
<CardPaymentForm
callback={callback}
validationPassed={() => actionsCollection.booking.saveCard(true, state.creditCard.lastFourDigits)}
formType="profileForm"
/>
<div>
<Button
type="submit"
onClick={(e) => callback.saveCard(e)}
value="SAVE CREDIT CARD"
className={classes.button}
/>
<div style={{ display: "flex", marginTop: 20 }}>
<img className={classes.lockIcon} src={lockIconInfo} alt="" />
<Typography className={classes.paymentInfo}>
<Link href="/terms" target={"_blank"}>
Terms of Payment
</Link>
.
</Typography>
</div>
</div>
</Box>
</Paper>
) : (
<div style={{ height: 373 }}>
<CardStored removeCard={removeCard} />
</div>
)}
</>
);
in CardPayementForm below calling the save function below is the code:
const CardPaymentForm = ({ classes, callback, validationPassed, formType, lastFourDigits }) {
useEffect(() => {
callback.saveCard = (e) => {
e.preventDefault();
=
if (validateForm()) {
=
validationPassed();
}
};
});
}
here without callback how to call save function directly in cardpaymentform, Any help please
I'm not sure this will apply to your problem but if you had a component_a
like
const ComponentA = ({handleClick}) => {
return(
<>
<button onClick(e => handleEvent(e))>
Click here
</button>
</>
}
and a component_b
const ComponentB = () => {
const handleClick = (e) => {
// do something with the event from component a
}
return(
<>
<ComponentA handleClick={handleClick}/>
</>
)
}

How to make links work with card-img-overlay React

I'm having an issue on my project. I created a card-img-overlay to display icons over an image. If you click on the entire image you are redirected to a post. I would like to make the like and share icons clickable.
My project is in Reactjs. I am displaying images and videos from Reddit API.
Thank you for your help.
id,
slugTitle,
title,
url_overridden_by_dest,
author,
preview,
}) => {
const [isVideo, setIsVideo] = useState(false);
useEffect(() => {
if (preview) setIsVideo(preview.split('.').pop() === 'mp4');
}, [preview]);
const history = useHistory();
const goToPage = () => {
history.push(`/Post/${id}/${slugTitle}`);
};
return (
<Card
inverse
onClick={goToPage}
style={{
cursor: 'pointer',
}}
>
{isVideo && (
<video autoPlay="false" loop width="100%" src={preview}>
<track default kind="captions" />
</video>
)}
{!isVideo && (
<CardImg top width="100%" src={url_overridden_by_dest} alt={title} />
)}
<CardImgOverlay className="hideinfos">
<CardText className="w-100 d-flex justify-content-between">
<div>
<VscAccount className="mr-2" size={20} />
{author}
</div>
<div>
<LikeButtonhp
className="mr-2 card-link"
size={20}
style={{
position: 'relative',
}}
/>
<BiShareAlt size={20} />
</div>
</CardText>
</CardImgOverlay>
</Card>
);
};
You'll need to put onClick handlers on your LikeButtonhp and BiShareAlt components, and use event.stopPropagation() to stop the event from bubbling up to the <Card />:
<BiShareAlt
size={20}
onClick={event => {
event.stopPropagation();
// Do stuff for share click
}}
/>
You may need to alter the BiShareAlt and LikeButtonhp components to support an onClick prop also, for example if they render a <button> element it may look like this:
const BiShareAlt = ({ onClick }) => (
<button onClick={onClick}>
Share
</button>
);
export default BiShareAlt;
In my onClick, I added an e.stopPropagation(); and it solves my problem. Now I can click on the heart icon and it works. It stops the onClick set up on my image (parent).
function LikeButtonhp() {
const [liked, setLiked] = useState(false);
return (
<Button
outline
color="link"
className="likebutton"
onClick={(e) => {
e.stopPropagation();
setLiked(!liked);
}}
style={{ color: 'white' }}
>
{liked ? <BsHeartFill size={20} /> : <BsHeart size={20} />}
</Button>
);
}

Resources