I want to remove a component from itself. So I tried creating a function in its parent and calling it from the component
I have an array with the states and I tried unmounting by removing the component from the array.
This is the parent. unmount is the function
import React, { useEffect } from "react";
import "./list.css";
import List_item from "./listitem";
function List(prop) {
const unmount = (what_to_unmount) => {
prop.itemremove(prop.items.pop(what_to_unmount));
};
let i = 0;
if (prop.items.length === 0) {
return <div></div>;
} else {
return (
<div className="list_item">
{prop.items.map((item) => {
if (item !== "") {
return <List_item name={item} unmount={prop.unmount} />;
} else {
return <div></div>;
}
})}
</div>
);
}
}
export default List;
I want to unmount this function on a button click
import React, { useEffect } from "react";
import { useState } from "react";
import "./listitem.css";
import DeleteIcon from "#material-ui/icons/Delete";
function List_item(props) {
const [check, setcheck] = useState(false);
useEffect(() => {
let checkbox = document.getElementById(`checkbox${props.name}`);
if (checkbox.checked) {
document.getElementById(props.name).style.textDecoration = "line-through";
} else {
document.getElementById(props.name).style.textDecoration = "none";
}
});
return (
<div>
<div className="item">
<span className="text" id={`${props.name}`}>
{props.name}
</span>
<div>
|{" "}
<input
type="checkbox"
id={`checkbox${props.name}`}
checked={check}
onClick={() => setcheck(!check)}
></input>{" "}
|
<span className="delete">
<DeleteIcon
onClick={() => {
props.unmount(props.name);
}}
/>
</span>
</div>
</div>
<hr className="aitem" />
</div>
);
}
export default List_item;
But it does not work.
Please help.
Related
I am finishing up a project where I want to use a small dropdown menu when I click on my settings icon. The problem is that for some reason it is not recognized when I click outside of that dropdown menu. I used a hook that worked in the same project with a different dropdown menu, but now it doesn't. Maybe because it is in a modal? I really don't know.
Here is the Repo of this Project: https://github.com/Clytax/fem-kanban
The Hook (I modified it a bit by excluding the elipsis icon so it doesnt reopen when Click on it to close it.)
import React from "react";
export const useOutsideClick = (callback, exclude) => {
const ref = React.useRef();
React.useEffect(() => {
const handleClick = (e) => {
if (
ref.current &&
!ref.current.contains(e.target) &&
!exclude.current.contains(e.target)
) {
callback();
}
};
document.addEventListener("click", handleClick);
return () => {
document.removeEventListener("click", handleClick);
};
}, [callback, exclude]);
return ref;
};
The Modal:
import React, { useRef, useState, useEffect } from "react";
import "./taskModal.scss";
import { ReactComponent as Elipsis } from "../../../assets/Icons/icon-vertical-ellipsis.svg";
import { ReactComponent as Close } from "../../../assets/Icons/icon-chevron-up.svg";
import { ReactComponent as Open } from "../../../assets/Icons/icon-chevron-down.svg";
import { useSelector, useDispatch } from "react-redux";
import modalSlice, {
closeViewTaskModal,
openEditTaskModal,
closeAllModals,
openDeleteTaskModal,
} from "../../../features/global/modalSlice";
import Backdrop from "../Backdrop/Backdrop";
import Subtask from "../../Task/Subtask";
import "../../Extra/DropdownSettings.scss";
import { useOutsideClick } from "../../../hooks/useOutsideClick";
import { motion } from "framer-motion";
import DropdownStatus from "../../Extra/DropdownStatus";
import DropdownSettings from "../../Extra/DropdownSettings";
import DropdownSettingsTask from "../../Extra/DropdownSettingsTask";
const ViewTaskModal = ({ handleClose }) => {
const [openSettings, setOpenSettings] = useState(false);
const dispatch = useDispatch();
const task = useSelector((state) => state.modal.viewTaskModal.task);
const handleCloseSettings = () => {
console.log("hi");
setOpenSettings(false);
};
const modal = useSelector((state) => state.modal);
const viewTaskModal = useSelector((state) => state.modal.viewTaskModal);
const elipsisRef = useRef(null);
const wrapperRef = useOutsideClick(handleCloseSettings, elipsisRef);
const getFinishedSubTasks = () => {
let finishedSubTasks = 0;
task.subTasks.forEach((subtask) => {
if (subtask.isDone) {
finishedSubTasks++;
}
});
return finishedSubTasks;
};
const closeModal = () => {
dispatch(closeViewTaskModal());
};
return (
<Backdrop onClick={closeModal} mobile={false}>
<motion.div
onClick={(e) => {
e.stopPropagation();
}}
onMouseDown={(e) => {
e.stopPropagation();
}}
className="view-task"
>
<div className="view-task__header | flex">
<h2 className="view-task__header__title">{task.name}</h2>
<div className="view-tastk__settings">
<div
className="view-task__header__icon"
style={{ cursor: "pointer" }}
ref={elipsisRef}
onClick={() => {
setOpenSettings(!openSettings);
}}
>
<Elipsis />
</div>
{openSettings && (
<div className="dropdown-settings__task" ref={wrapperRef}>
<div
className="dropdown-settings__item"
onClick={() => {
dispatch(closeAllModals());
dispatch(openEditTaskModal(task));
}}
>
Edit Task
</div>
<div
className="dropdown-settings__item"
onClick={() => {
dispatch(closeAllModals());
dispatch(openDeleteTaskModal(task));
}}
>
Delete Task
</div>
</div>
)}
</div>
</div>
<p className="view-task__description">{task.description}</p>
<div className="view-task__subtasks">
<p>
Subtasks ({getFinishedSubTasks()} of {task.subTasks.length})
</p>
<div className="view-task__subtasks__list">
{task.subTasks.map((subtask, index) => (
<Subtask
subtaskID={subtask.id}
boardID={task.boardID}
taskID={task.id}
columnID={task.columnID}
key={index}
/>
))}
</div>
</div>
<div className="view-task__status">
<p>Current Status</p>
<DropdownStatus click={handleCloseSettings} task={task} />
</div>
</motion.div>
</Backdrop>
);
};
export default ViewTaskModal;
I want to change the amount of a product in the preview CMP that is rendered by the list CMP.
The preview changes the data and the list (father CMP) I set the updated data in the state but the cmps didn't re-rendering. the data is changed and the state is changed but the cmps are not.
the father CMP
import { useEffect, useState } from 'react'
import { productService } from '../services/product.service.js'
import { ProductPreview } from './ProductPreview.jsx'
import { utilService } from '../services/util.service.js'
export function ProductList() {
const [totalMoney, setTotalMoney] = useState(0)
const [spendMoney, setSpendMoney] = useState('You havent Spent yet !')
const [products, setProducts] = useState([])
useEffect(() => {
setTotalMoney(217000000000)
getProducts()
}, [])
const getProducts = () => {
setProducts(productService.getProducts())
}
if (products.length === 0) return <h1>loading...</h1>
return (
<main>
<div className="total">
<span> Remaining: {utilService.currencyFormat(totalMoney)}</span>
<span>{spendMoney}</span>
</div>
<ul className="products-list">
{products.map((product) => (
<ProductPreview
product={product}
key={product._id}
getProducts={getProducts}
/>
))}
</ul>
</main>
)
}
the Previw CMP
import { utilService } from '../services/util.service.js'
import { productService } from '../services/product.service.js'
import { useEffect, useState } from 'react'
export function ProductPreview({ product, getProducts }) {
const [currProduct, setCurrProduct] = useState(product)
const changeAmount = (diff) => {
const newProduct = productService.increasAmount(currProduct._id, diff)
setCurrProduct(newProduct)
getProducts()
}
return (
<li className="product-preview">
<img src={currProduct.img} alt="" />
<h1>{currProduct.name}</h1>
<h1>{utilService.currencyFormat(currProduct.price)}</h1>
<div className="actions">
<button onClick={() => changeAmount(-1)} className="sell">
Sell
</button>
<p>{currProduct.amount}</p>
<button onClick={() => changeAmount(1)} className="buy">
Buy
</button>
</div>
</li>
)
}
I am trying to display the SingleLineText component below that has one input field when a click occurs in the parent component called TextInput. However, after a click, the state is not changed by useState and as a result the child component named SingleLineText is not displayed.
TextInput component is pasted below right after SingleLineText component.
SingleLineText component:
import React from "react";
const SingleLineText = () => {
return(
<form>
<input />
</form>
)
}
export default SingleLineText;
TextInput the Parent component for SingleLineText component:
import React, {useState, useEffect} from "react";
import SingleLineText from "./SingleLineText"
const TextInput = (props) => {
const [showSingleText, setShowSingleText] = useState(false);
useEffect( () => {
}, [showSingleText])
const handleFieldDisplay = (event) => {
if (event.target.value == "Single line text") {
cancel();
setShowSingleText(showSingleText => !showSingleText);
//setShowSingleText(showSingleText => true);
}
}
const cancel = () => {
props.setShowInput(!props.showInput);
}
return (
<>
<div className="dropdown">
<ul key={props.parentIndex}>
{
props.fieldType.map( (val, idx) => {
return(
<option key={idx} value={val} className="dropdown-item" onClick={handleFieldDisplay}> {val} </option>
)
})
}
</ul>
</div>
{showSingleText && <SingleLineText />}
</>
)
}
export default TextInput;
Topmost or grand parent component:
import React, { useState, useEffect, useRef } from "react"
import PropTypes from "prop-types"
import TextInput from "components/pod_table/fields/inputs/TextInput";
const DynamicFields = (props) => {
const fieldType = ['Checkbox', 'Dropdown', 'boolean', 'Single line text'];
const [showDynamicField, setShowDynamicField ] = useState(false);
const[showInput, setShowInput] = useState(false);
useEffect(() => {}, [showDynamicField, showInput]);
const handleShowDynamicField = (event) => {
setShowDynamicField(!showDynamicField);
}
const handleSubDisplay = (event) => {
if (event.target.value == "customise field type") {
setShowDynamicField(!showDynamicField);
setShowInput(!showInput);
}
}
return (
<React.Fragment>
<div>
<i className="bi bi-chevron-compact-down" onClick={handleShowDynamicField}></i>
{ showDynamicField &&
(<div className="dropdown">
<ul key={props.parentIndex}>
{
optionsHash.map( (val, idx) => {
return(
<option key={idx} value={val} className="dropdown-item" onClick={handleSubDisplay}> {val} </option>
)
})
}
</ul>
</div>) }
{showInput && <TextInput fieldType={fieldType} setShowInput={setShowInput} showInput={showInput} /> }
</div>
</React.Fragment>
)
}
The problem is that in the handleFieldDisplayFunction you are calling the cancel function which changes the parent state showInput and hence unmounts the TextInput component itself, so the showSingleText state changes inside TextInput component doesn't have any effect.
Design your code in a way that you are not required to unmount TextInput when you click on an option within it
const handleFieldDisplay = (event) => {
if (event.target.value == "Single line text") {
setShowSingleText(showSingleText => !showSingleText);
}
}
Two components talking to each other, when adding a product to the cart (component # 1), update the setState, via service (component # 2).
When adding the product to the cart, an error returns, saying that I don't have access to the export function of component # 1.
#1 Component
import React, { useContext, useEffect } from 'react';
import Link from 'next/link';
import { Clipboard } from 'react-feather';
import { OrderCartButton } from './styles';
import OrderService from '~/services/orderservice';
import Context from '~/utils/context';
function OrderCart() {
const { state, actions } = useContext(Context);
function updateQty() {
const qty = OrderService.count();
actions({ type: 'setState', payload: { ...state, value: qty } });
}
useEffect(() => {
updateQty();
}, []);
return (
<>
<Link href="/order">
<OrderCartButton data-order-qty={state.value}>
<Clipboard />
</OrderCartButton>
</Link>
</>
);
}
export default OrderCart;
#2 Component
import { reactLocalStorage } from 'reactjs-localstorage';
import { toast } from 'react-toastify';
class OrderService {
async add(product) {
const oldorder = reactLocalStorage.getObject('_order_meumenu');
if (oldorder.length) {
const merged = [...oldorder, ...product].reduce(
(r, { id, qty, title, description, price, image }) => {
const item = r.find((q) => q.id === id);
if (item) item.qty += qty;
else r.push({ id, qty, title, description, price, image });
return r;
},
[]
);
await reactLocalStorage.setObject('_order_meumenu', merged);
} else {
await reactLocalStorage.setObject('_order_meumenu', product);
}
toast.success('Produto adicionado ao Pedido');
const qty = await this.count();
return qty;
},
async count() {
const order = reactLocalStorage.getObject('_order_meumenu');
return order.length || 0;
},
}
export default OrderService;
Component #3 - Context Moved to callback
import React, { useState, useContext } from 'react';
import { Plus, Minus } from 'react-feather';
import { ProductContainer } from './styles';
import currency from '../../utils/currency';
import OrderService from '~/services/orderservice';
import Context from '~/utils/context';
function Product(product) {
const { state, actions } = useContext(Context);
const [qty, setQty] = useState(1);
function addProductOrder(elem, elemQty) {
// eslint-disable-next-line no-param-reassign
const newElem = [];
newElem.push({ ...elem, qty: elemQty });
OrderService.add(newElem).then((val) =>
actions({ type: 'setState', payload: { ...state, value: val } })
);
}
return (
<ProductContainer>
<div className="product-image">
<img
className="image"
src={product.image}
alt={product.title}
data-product-image
/>
</div>
<div className="product-details">
<div className="product-top">
<div className="product-title">
<span className="title" data-product-title>
{product.title}
</span>
<span className="desc" data-product-desc>
{product.description}
</span>
</div>
<button
type="button"
className="product-add"
onClick={() => addProductOrder(product, qty)}
>
<span className="btn -icon -rounded" title="Add Produto">
<Plus className="icon" />
</span>
</button>
</div>
<div className="product-bottom">
<div className="product-qty">
<div className="product-control-number">
<Minus className="icon" onClick={() => setQty(qty - 1)} />
<input
className="input"
type="number"
min="1"
max="9"
value={qty}
readOnly
data-number-value
/>
<Plus className="icon" onClick={() => setQty(qty + 1)} />
</div>
</div>
<div
className="product-price"
data-product-price={product.price}
data-product-totalprice="9"
>
{currency(product.price)}
</div>
</div>
</div>
</ProductContainer>
);
}
export default Product;
There are a few things to fix:
import OrderCart from '~/components/OrderCart';
// ...
OrderCart.updateQty();
the default export in ~/components/OrderCart is the class component(OrderCart) and updateQty is another function in the same file, so the import statement should be something like:
import { updateQty } from '~/components/OrderCart';
and the usage should be
updateQty()
but this will not work because calling a function that returns some object will not cause a rerender.
So, to fix this you should pass a callback to the child component that calls the add function, and call the callback after invoking add.
The callback function to pass as props to the child can be handleUpdateQty.
I have the problem when I use the Reactjs, I'm really new to Reactjs, so maybe it's a easy problem
I want to use the class ClickButton in the UserInfo,but I don't know how to change the name through props
import React, { PropTypes } from 'react';
import { Button } from 'antd';
import { connect } from 'react-redux';
import styles from './ClickButton.less';
const ClickButton = ({ todos,dispatch }) => {
const userinforclick = () => {
dispatch({
type: 'todos/clickbutton',
payload: !todos['click_button'],
});
};
return (
<span>
< span type="primary" className={ styles.show } onClick={ userinforclick.bind(this) } > {this.props.name} < /span >
</span>
);
};
function clickbutton({ todos }){
return{
todos:todos,
}
}
export default connect(clickbutton)(ClickButton)
and i use the ClickButton in UserInfo:
import React from 'react'
import styles from './Userinfo.less'
import ClickButton from '../../components/Button/ClickButton'
import { connect } from 'react-redux';
import { Spin } from 'antd'
const Userinfo = ({ todos,dispatch }) => {
const { userinfo, userinfoloading, click_button } = todos;
if(userinfoloading) {
return <Spin />;
}
const renderList = () => {
return(
<div className={ styles.userInfodiv}>
<div>
<span className={ styles.userInfoTitle }>userinfo</span>
</div>
<div className = { styles.slice }></div>
<div className = { styles.userInfoBody}>
<div className = { styles.userInfoSubBody }>
<span>username:</span>
<span>{userinfo[0]['username']}</span>
</div>
<div className = { styles.userInfoSubBody }>
<span>number:</span>
{ click_button ? <span>{userinfo[0]['phone']}</span> : <input type="text" value={userinfo[0]['phone']} /> }
<ClickButton name="john" />
</div>
</div>
</div>
);
};
return (
<div>
{ renderList() }
</div>
);
};
function mapStateToProps({ todos }) {
return {
todos: todos,
};
}
export default connect(mapStateToProps)(Userinfo);
Here's something that actually works (although I removed the todos and such but you can add them in easily):
class RenderList extends React.Component {
render() {
return (<span> {this.props.name} </span>);
}
}
class App extends React.Component {
render() {
return (<div>
<RenderList name="John"/>
</div>)
}
}
ReactDOM.render(<App/>,document.getElementById("app"));