set function does not trigger an update in React with Recoil - reactjs

This seems like it should be easy but why isn't a button callback function with a setState call not triggering a refresh of the data item? Actually it's just the computeSMA button that isn't changing the sma when the button is selected. The other two callbacks to set inputs work. The fetchData updates the charts so i can't figure this out!! Must be too tired ...
import React, { useState, useEffect } from "react"
import { useRecoilState } from "recoil";
import { closingDataAtom, metaDataAtom, tickerAtom, timeSeriesAtom , smaAtom} from '../../utils/atoms'
import { Container } from '#material-ui/core'
import '../../utils/Home.css'
import { VictoryChart, VictoryBar, VictoryTheme, VictoryVoronoiContainer, VictoryLine, VictoryBrushContainer, VictoryZoomContainer } from 'victory';
import { Chart, Axis, Tooltip, Line, Point } from "bizcharts";
import {XYPlot, LineSeries} from 'react-vis';
const APIKEY = 'demo'
const Home = () => {
const [apikey, setApiKey] = useState(APIKEY)
const [ticker, setTicker] = useRecoilState(tickerAtom);
const [metadata, setMetaData] = useRecoilState(metaDataAtom)
const [closingdata, setClosingData] = useRecoilState(closingDataAtom)
const [dates, setDates] = useRecoilState(timeSeriesAtom)
const [sma, setSMA] = useRecoilState(smaAtom)
const TIME_RESOLUTION = 'Daily'
var requestUrl
if (TIME_RESOLUTION === 'Daily') {
requestUrl = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=" + ticker + "&outputsize=full&apikey=" + apikey
} else {
requestUrl = "https://www.alphavantage.co/query?function=TIME_SERIES_WEEKLY_ADJUSTED&symbol=" + ticker + "&outputsize=full&apikey=" + apikey;
}
const fetchData = async () => {
fetch(requestUrl)
.then(response => response.json())
.then(data => {
var closing_price = []
var metadata = []
var dat = []
Object.keys(data['Time Series (Daily)']).forEach((dateKey) => {
closing_price.push(data['Time Series (Daily)'][dateKey]['5. adjusted close'])
dat.push({ 'date': new Date(dateKey) })
})
Object.keys(data['Meta Data']).forEach((metaKey) => {
metadata.push(data['Meta Data'][metaKey])
})
setDates(dat.reverse())
setClosingData(closing_price.reverse())
setMetaData(metadata)
})
.catch(e => {
});
};
const handleSMACompute = (e) => {
var sm = ['2', '3', '4']
setSMA(sm) <====== REACT IS NOT "REACTING"
}
const handleTickerInput = (e) => {
setTicker(e.target.value)
}
const handleAPIInput = (e) => {
setApiKey(e.target.value)
}
return (
<>
<Container className="container" maxWidth="sm">
<div>
<label>Ticker:</label> {ticker}
<input type="text" name="ticker" onChange={handleTickerInput} />
</div>
<div>
<label>APIKEY:</label> {apikey}
<input type="text" name="apikey" onChange={handleAPIInput} />
</div>
<button onClick={fetchData}>
Click it
</button>
<Container className="container" maxWidth="sm">
<ul>{metadata}</ul>
</Container>
<button OnClick={handleSMACompute}> Generate SMA </button>
<Container className="container" maxWidth="sm">
<ul>The value is {sma}</ul>
</Container><div>
</div>
<VictoryChart
theme={VictoryTheme.material}
domainPadding={10}
>
<VictoryBar
style={{ data: { fill: "#c43a31" } }}
data={closingdata}
/>
</VictoryChart>
<div>
<VictoryChart
theme={VictoryTheme.material}
>
<VictoryLine
style={{
data: { stroke: "#c43a31" },
parent: { border: "1px solid #ccc" }
}}
animate={{
duration: 20,
onLoad: { duration: 20 }
}}
containerComponent={<VictoryZoomContainer zoomDomain={{x: [5, 35], y: [0, 100]}}/>}
categories={{
y: dates
}}
data={closingdata}
/>
</VictoryChart>
</div>
</Container>
</>
);
}```

It seems to have been the button setup. I changed to this and it works....??ggrrrr
Click it
</button>
<Container className="container" maxWidth="sm">
<li>{metadata}</li>
</Container>
<button onClick={computeSMA}>
Click it
</button>
<Container className="container" maxWidth="sm">
<li>{sma}</li>
</Container>

In your first code, you used OnClick as the event name. Should be onClick. It is a react syntax and it is case sensitive.

Related

useState([]) returns undefined instead of empty array using context

I am quite new to react and i´m tryng to make a wishlist using context. I have done something similar for my cart so I coppied all the code from there, but I cannot figure out why this is not working. When I call the array "wishlist" it returns undefined instead of an empty array despite having used useState([]) in the wishlist Context. (I DID THE SAME WITH THE CART AND WORKS PERFECTLY!) Here I leave the code:
wishlistContext.js
import { createContext, useContext, useState } from "react";
const WishlistContext = createContext([]);
export const useWishlistContext = () => useContext(WishlistContext);
export const WishlistProvider = ({ children }) => {
const [wishlist, setWishlist] = useState([]); // HERE I SET "wishlist" AS AN EMPTY ARRAY
const value = {
wishlist,
};
return (
<WishlistContext.Provider value={value} displayName="wishlistContext">
{children}
</WishlistContext.Provider>
);
};
Wishlist.jsx
import { useEffect, useState } from "react";
import { useWishlistContext } from "../context/wishlistContext";
export const Wishlist = () => {
const { wishlist } = useWishlistContext();
console.log(wishlist); // THIS IS WHAT RETURNS UNDEFINED
return (
<div className="w-75 m-auto my-5">
Something
</div>
);
};
NOW I LEAVE THE SAME EXAMPLE WITH CART THAT WORKS FLAWLESSLY
cartContext.js
import { createContext, useContext, useState } from "react";
const CartContext = createContext([]);
export const useCartContext = () => useContext(CartContext);
export const CartProvider = ({ children }) => {
const [cart, setCart] = useState([]); // HERE I SET "cart" AS AN EMPTY ARRAY
const removeProduct = (id) => {
const newCart = cart.filter((product) => product.id !== id);
setCart(newCart);
};
const addProduct = (item, qty) => {
const element = cart.find((product) => product.id === item.id);
if (!element) return setCart([...cart, { ...item, qty }]);
const newCart = cart.map((product) => {
if (product.id === item.id) {
return { ...product, qty: product.qty + qty };
}
return product;
});
setCart(newCart);
};
const getTotal = () =>
cart.reduce((acc, product) => acc + product.valor * product.qty, 0);
const getCartQty = () => cart.reduce((acc, product) => acc + product.qty, 0);
const emptyCart = () => setCart([]);
const value = {
cart,
addProduct,
removeProduct,
getCartQty,
getTotal,
emptyCart,
};
return (
<CartContext.Provider value={value} displayName="cartContext">
{children}
</CartContext.Provider>
);
};
Cart.jsx
import { BsFillCartFill } from "react-icons/bs";
import { useEffect, useState } from "react";
import { addOrder } from "../api/orders";
import { updateManyProducts } from "../api/products";
import { useCartContext } from "../context/cartContext";
import Swal from "sweetalert2";
export const Cart = () => {
const { getTotal, cart, emptyCart } = useCartContext();
console.log(cart);
if (cart.length <= 0)
return (
<div className="d-flex justify-content-evenly">
<div className="text-center m-auto" style={{ fontWeight: 600 }}>
<BsFillCartFill /> <br /> Su carrito esta vacío
</div>
</div>
);
const createOrder = async (e) => {
e.preventDefault();
const items = cart.map(({ id, nombre, qty, valor }) => ({
id,
nombre,
qty,
valor,
}));
let itemsAlert = "";
for (let i = 0; i < items.length; i++) {
itemsAlert +=
"<b>Item:</b> " +
items[i].nombre +
"<br><b>Cantidad:</b> " +
items[i].qty +
" <b>Valor Unidad:</b> $" +
items[i].valor +
"<br><br>";
console.log(itemsAlert);
}
const order = {
buyer: { name, phone, email },
items,
fecha: newdate,
estado: { orderState },
total: getTotal(),
};
const { buyer, fecha, total } = order;
const id = await addOrder(order);
await updateManyProducts(items);
emptyCart();
Swal.fire({
title: "Pedido Realizado",
html: `El id de su compra es <b>"${id}"</b> <br> Fecha: ${fecha}<br><br> <b>Detalle de compra:</b><br> <br> <b>Comprador:</b> ${buyer.name} <br><br> ${itemsAlert}<br> <b>Total: $${total}</b>`,
icon: "success",
confirmButtonText: "OK",
});
};
return (
<div className="w-75 m-auto my-5">
<h2 className="text-center my-5">
<BsFillCartFill /> Carrito <BsFillCartFill />{" "}
</h2>
{cart.map((product) => (
<div
key={product.id}
style={{
display: "flex",
gap: 50,
height: 100,
alignItems: "center",
width: "70%",
justifyContent: "space-evenly",
}}
className="m-auto"
>
<div>
Producto :{" "}
<b>
<b>{product.nombre}</b>
</b>
</div>
<div>
Valor unitario :{" "}
<b>
<b>${product.valor}</b>
</b>
</div>
<div>
Cantidad :{" "}
<b>
<b>{product.qty}</b>
</b>
</div>
</div>
))}
<span
style={{
marginBottom: 50,
textAlign: "center",
width: "70%",
fontSize: 20,
}}
className="mx-auto"
>
Total :{" "}
<b>
<b>${getTotal()}</b>
</b>
</span>
<form style={{ display: "grid", gap: 10 }} className="mb-5">
<span>Nombre</span>
<input
style={{ border: "1px solid black", height: 40 }}
onChange={(e) => setName(e.target.value)}
/>
<span>Telefono</span>
<input
type={"phone"}
style={{ border: "1px solid black", height: 40 }}
onBlur={(e) => validateInput(e.target.value, "phone")}
/>
<span>Email</span>
<input
type={"email"}
style={{ border: "1px solid black", marginBottom: 15, height: 40 }}
onBlur={(e) => validateInput(e.target.value, "email")}
/>
<span>Confirmar Email</span>
<input
style={{ border: "1px solid black", marginBottom: 15, height: 40 }}
onBlur={(e) => checkEmail(e.target.value)}
/>
<input type="submit" value="Enviar" onClick={createOrder} />
</form>
<div>
{mailError}
<br />
{phoneError}
</div>
</div>
);
};
I dont know if this has to do with some async property that i´m missing. Truth is I literally replicated the cart code, and this outcome really surprised me. Thank you all beforehand, this is giving me a big headache.
The JSX component must be wrapped by the context provider. Not sure if that is the issue without seeing the parent of your Wishlist component. Here is how I recreated the project and resolved issue:
Create React App 5.0.1
npx create-react-app test
cd test
npm start
Copy wishlistContext.js to src/context/
Copy wishlist.jsx to src/
To reproduce the issue, modify App.js to the following:
import './App.css';
import { Wishlist } from './wishList';
function App() {
return (
<Wishlist />
);
}
export default App;
See undefined in the console
Wrap the JSX element with the context provider
import './App.css';
import { Wishlist } from './wishList';
import { WishlistProvider } from './context/wishlistContext';
function App() {
return (
<WishlistProvider>
<Wishlist />
</WishlistProvider>
);
}
export default App;
See [] in the console

onRemove Upload Image antDesign + delete item from interface

I have a project, and this project contains several interfaces, and among these interfaces there is an interface for uploading an image, and the problem is in the deletion icon. When you click on it, a modal appears, but the element is deleted before the modal appears.
How can i solve the problem?
this file display a list of instructions that contains upload Image
import '../../../styles/input/index.scss';
import '../../../styles/dropzone/index.scss';
import { Button, Col, message, Modal, Row, Spin, Upload, UploadFile } from 'antd';
import { FunctionComponent, useCallback, useRef, useState } from 'react';
import { motion, useAnimation } from 'framer-motion';
import { defaultTranstion } from '../../../constants/framer';
import { Controller } from 'react-hook-form';
import FromElemnetWrapper from '../form-element-wrapper';
import { Delete, UploadCloud } from 'react-feather';
import { getBase64 } from '../../../utils/get-base64';
import _ from 'lodash';
import config from '../../../api/nuclearMedicineApi/config';
import { FormattedMessage } from 'react-intl';
import BasicModal from '../modal';
import { UploadOutlined } from '#ant-design/icons';
import axios from 'axios';
import { IFormError } from '../general-form-containner';
interface DropzoneProps {
name: string;
control: any;
rules?: any;
label: string;
disabled?: boolean;
multiple?: boolean;
accept?: string;
refType?: number;
defaultFileList?: any;
onRemove?: any;
customRequest?: (option: any) => void;
onProgress?: any;
}
const Dropzone: FunctionComponent<DropzoneProps> = ({
name,
control,
rules,
label,
disabled,
multiple,
accept,
refType,
defaultFileList,
onRemove,
customRequest,
onProgress
}) => {
const focusController = useAnimation();
const errorController = useAnimation();
const [previewVisible, setpreviewVisible] = useState(false);
const [previewImage, setpreviewImage] = useState('');
const handleCancel = () => setpreviewVisible(false);
const handlePreview = async (file: any) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
setpreviewImage(file?.preview ?? file.url);
setpreviewVisible(true);
};
const [isModalOpen, setIsModalOpen] = useState(false);
const [errors, setErrors] = useState<IFormError[]>([]);
const [visibleModal, setVisibleModal] = useState(false);
const [removePromise, setRemovePromise] = useState();
const [deleteVisible, setdeleteVisible] = useState<boolean>(false);
const onDeleteHandle = () => {
setdeleteVisible(false);
};
const deletehandleCancel = () => {
setdeleteVisible(false);
};
let resolvePromiseRef = useRef<((value: boolean) => void) | undefined>();
// let resolvePromiseRef = useRef<HTMLInputElement | null>(null)
const handleRemove = useCallback(() =>{
const promise = new Promise(resolve => {
resolvePromiseRef.current = resolve
});
setVisibleModal(true);
return promise;
}, [])
const handleOkModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(true)
}
}, [removePromise]);
const handleCancelModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(false);
setVisibleModal(false)
}
}, [removePromise]);
return (
<>
<FromElemnetWrapper
focusController={focusController}
errorController={errorController}
label={label}
required={rules.required?.value}
>
<Controller
control={control}
name={name}
rules={rules}
render={({
field: { onChange, onBlur, value, name, ref },
fieldState: { invalid, error },
}) => {
if (invalid) {
errorController.start({ scale: 80 });
} else {
errorController.start(
{ scale: 0 },
{ ease: defaultTranstion.ease.reverse() },
);
}
return (
<div
onBlur={() => {
onBlur();
focusController.start({ scale: 0 });
}}
onFocus={() => {
focusController.start({ scale: 80 });
}}
className='relative'
>
<div className='upload-container'>
<form
className='dropzone needsclick'
id='demo-upload'
action='/upload'
>
{/* <> */}
<Upload
action={`${config.baseUrl}api/services/app/Attachment/Upload`}
headers={config.headers}
ref={ref}
multiple={multiple}
disabled={disabled}
data={{ RefType: refType }}
listType='picture'
fileList={value}
id={name}
accept={accept}
onPreview={handlePreview}
onRemove={handleRemove}
iconRender={
() => {
return <Spin style={{ marginBottom: '12px', paddingBottom: '12px' }}></Spin>
}
}
progress={{
strokeWidth: 3,
strokeColor: {
"0%": "#f0f",
"100%": "#ff0"
},
style: { top: 12 }
}}
beforeUpload={
(file) => {
console.log({ file });
return true
}
}
// onProgress= {(event: any) => (event.loaded / event.total) * 100}
// onChange={(e) =>
// onChange(e.fileList)
// }
// onChange={(response) => {
// console.log('response: ', response);
// if (response.file.status !== 'uploading') {
// console.log(response.file, response.fileList);
// }
// if (response.file.status === 'done') {
// message.success(`${response.file.name}
// file uploaded successfully`);
// } else if (response.file.status === 'error') {
// message.error(`${response.file.name}
// file upload failed.`);
// }
// else if (response.file.status === 'removed') {
// message.error(`${response.file.name}
// file upload removed.`);
// }
// }}
>
<div className='upload-button'>
<div className='wrapper'>
<motion.div
className='fas fa-angle-double-up'
whileHover={{
y: [
0, -2, 2,
0,
],
transition: {
duration: 1.5,
ease: 'easeInOut',
yoyo: Infinity,
},
}}
>
<UploadCloud
style={{
margin: '.2rem',
display:
'inline-block',
}}
color='white'
size={20}
/>
Upload
</motion.div>
</div>
</div>
</Upload>
{/*
<Modal
title="Are you sure?"
visible={visibleModal}
onOk={handleOkModalRemove}
onCancel={handleCancelModalRemove}
/> */}
<BasicModal
header={
<>
<FormattedMessage id={'confirmdeletion'} />
</>
}
headerType='error'
content={
<>
<Row>
<Col span={8} offset={4}>
<Button
type='primary'
className='savebtn'
onClick={onDeleteHandle}
style={{
cursor:
Object.keys(errors).length !==
0
? 'not-allowed'
: 'pointer',
}}
>
<FormattedMessage id={'affirmation'} />
</Button>
</Col>
<Col span={8} offset={4}>
<Button
type='default'
className='savebtn'
onClick={deletehandleCancel}
style={{
cursor:
Object.keys(errors).length !==
0
? 'not-allowed'
: 'pointer',
}}
>
<FormattedMessage id={'cancel'} />
</Button>
</Col>
</Row>
</>
}
isOpen={visibleModal}
footer={false}
width='35vw'
handleCancel={handleCancelModalRemove}
handleOk={handleOkModalRemove}
/>
{/* {_.isEmpty(value) && (
<div className='dz-message needsclick'>
<FormattedMessage id='dropfileshere' />
</div>
)} */}
<BasicModal
isOpen={previewVisible}
header={<FormattedMessage id="Preview image" />}
footer={false}
handleCancel={handleCancel}
content={<img
alt='example'
style={{ width: '100%' }}
src={previewImage}
/>}
/>
{/* </> */}
</form>
</div>
{invalid && (
<p className='form-element-error'>
{error?.message}
</p>
)}
</div>
);
}}
/>
</FromElemnetWrapper>
</>
);
};
export default Dropzone;
Why it doesn't work?
https://ant.design/components/upload
onRemove - A callback function, will be executed when removing file button is clicked, remove event will be prevented when return value is false or a Promise which resolve(false) or reject
You have to return a promise which resolves to false or return false
How to solve it?
You have to return a promise so first of all, you need a ref or a state to store resolve function of that promise so you can call it in modal
const resolvePromiseRef = useRef<((value: boolean) => void) | undefined>();
Then you will need to assign the resolve function to the ref and return the promise
Now onRemove will wait for your promise to resolve
const handleRemove = () =>{
const promise = new Promise(resolve => {
resolvePromiseRef.current = resolve
});
setVisibleModal(true);
return promise;
}
Now your functions handlers
const handleOkModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(true)
}
}, [removePromise]);
const handleCancelModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(false)
}
}, [removePromise]);
You can also use the state, but I recommend ref because it doesn't rerender the component when changed.

How can I reset a dragged event to its original position on some condition in fullcallendar

I am working on fullCalendar. I want to implement something like this:
After dragging and dropping an event, a popup shows
If we cancel the event it needs to go back to its previous position
But the issue I am facing is that I am not able to find a way to return event back to its original position after dragging.
I am thinking to store the start dragging position and store also store the drop position. But I don't know if it is the right why to do that. Is there is any built in feature to do that or set the event back?
import React, { useState, useRef, useEffect } from "react";
import FullCalendar from "#fullcalendar/react"; // must go before plugins
import dayGridPlugin from "#fullcalendar/daygrid"; // a plugin!
import interactionPlugin, { Draggable } from "#fullcalendar/interaction"; // needed for dayClick
import timeGridPlugin from "#fullcalendar/timegrid";
import { resources } from "./records/resources";
import { events } from "./records/events";
import { Col, Row } from "react-bootstrap";
import "#fullcalendar/daygrid/main.css";
import "#fullcalendar/timegrid/main.css";
import "bootstrap/dist/css/bootstrap.css";
import { AlertBox } from "./atertBox";
import resourceTimelinePlugin from "#fullcalendar/resource-timeline";
function App() {
const [tableState, setTableState] = useState(events);
const [show, setShow] = useState(false);
const [showModal, setShowModal] = useState(false);
const dateRef = useRef();
const addEvent = (e) => {
const newEvent = {
id: tableState.length + 1,
title: e.target.title.value,
start: e.target.startdate.value + ":00+00:00",
end: e.target.enddate.value + ":00+00:00",
resourceId: "d",
// resourceEditable: false, to Preventing shifting between resources
};
setTableState((prev) => {
console.log(...prev);
console.log();
console.log(newEvent);
return [...prev, newEvent];
});
e.preventDefault();
};
const handleDateClick = (arg) => {
console.log("Arg", arg.dateStr);
};
const saveRecord = (record) => {
var today = new Date(record);
// var dd = String(today.getDate()).padStart(2, "0");
// var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0!
// var yyyy = today.getFullYear();
// today = yyyy + "-" + mm + "-" + dd;
console.log(today, "today");
dateRef.current = { today };
setShow(true);
};
const injectwithButton = (args) => {
return (
<div>
<button onClick={() => saveRecord(args.date)}>
{args.dayNumberText}
</button>
</div>
);
};
useEffect(() => {
let draggableEl = document.getElementById("external-events");
new Draggable(draggableEl, {
itemSelector: ".fc-event",
minDistance: 5,
eventData: function (eventEl) {
console.log("drag element ", eventEl);
let title = eventEl.getAttribute("title");
let id = eventEl.getAttribute("data");
return {
title: title,
id: id,
};
},
});
}, []);
const eventClicked = (event) => {
console.log("event",event)
};
return (
<div className="animated fadeIn p-4 demo-app">
<Row>
<Col lg={3} sm={3} md={3}>
<div
id="external-events"
style={{
padding: "10px",
width: "20%",
height: "auto",
}}
>
<p align="center">
<strong> Events</strong>
</p>
{events.map((event, index) => (
<div
className="fc-event"
title={event.title}
data={event.id}
key={index}
>
{event.title}
</div>
))}
</div>
</Col>
<Col lg={9} sm={9} md={9}>
<div className="demo-app-calendar" id="mycalendartest">
<FullCalendar
plugins={[
dayGridPlugin,
timeGridPlugin,
interactionPlugin,
resourceTimelinePlugin,
]}
initialView="resourceTimeline"
headerToolbar={{
left: "today,prev,next",
center: "title",
right:
"new dayGridMonth,timeGridWeek,timeGridDay,resourceTimeline",
}}
customButtons={{
new: {
text: "add new event",
click: (e) => setShow(true),
},
}}
eventColor="grey"
nowIndicator={true}
events={tableState}
resources={resources}
dateClick={handleDateClick}
eventClick={eventClicked}
editable={true}
eventDragStart={(e) => console.log("ee", e)}
eventDrop={(drop) => {
console.log("drop", drop.oldEvent._instance.range);
console.log("drop", drop.oldEvent._def.publicId);
}}
eventResizableFromStart={true}
eventResize={(resize) => console.log("resize", resize)}
dayCellContent={injectwithButton}
schedulerLicenseKey="CC-Attribution-NonCommercial-NoDerivatives"
droppable={true}
// eventReceive={(e) => {
// console.log("receive", e);
// console.log("receive", e.event._instance.range);
// }}
drop={(drop) => {
console.log("external drop", drop);
}}
/>
</div>
</Col>
</Row>
</div>
);
}
export default App;
If you're talking about dragging an existing calendar event to a new position, then eventDrop provides a revert callback which, if called, will send the event back to its previous position.
If you're talking about dragging an external event onto the calendar, eventReceive provides a revert callback which, if called, will reverse the action and the event will not be dropped onto the calendar.

React page won't load when fatch data function has 3 items

I have this detail page when the detail button is clicked it goes to the detail page. However, sometimes the page won't load. The fatchData function has 3 items and each one calls the webapi to get different dataset. If I remove any of 2 in fatchData function and only leave one in it and it will load fine. However, the rest of the data will be missing and I need it in the page. How do I solve it? I think it has something to do with the useEffect hook that causes the stackover flow.
Here is the error it always hit: throw Error( "Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops." );
Here is the entire page code:
import React, { useEffect, useState } from 'react';
import 'dotenv/config';
import Button from '#mui/material/Button';
import MenuItem from '#mui/material/MenuItem';
import Select from '#mui/material/Select';
import ClearIcon from '#mui/icons-material/Clear';
import ListItem from '#mui/material/ListItem';
import ListItemIcon from '#mui/material/ListItemIcon';
import ListItemButton from '#mui/material/ListItemButton';
import ListItemText from '#mui/material/ListItemText';
import List from '#mui/material/List';
import { useParams } from 'react-router-dom'
// Fields
import TextField from '#mui/material/TextField';
// Table
import scriptResultTableColumns from './../table/ScriptGroupResultTable';
import TableComponent from './../table/TableComponent';
import { InputLabel } from '#mui/material';
import FormControl from '#mui/material/FormControl';
function ScriptGroupResultsTable(props) {
const columns = scriptResultTableColumns();
return (<TableComponent columns={columns} url={`${process.env.REACT_APP_API_URL}/api/script-
group-result/script-group-results-by-group-id?id=` + props.groupid} buttonVisible={false}
tableType={'script-group-detail'} />);
}
export default function ScriptDetailsPage(props) {
const [validationError] = useState(null);
const [item, setItem] = useState([]);
const [scriptList, setScriptList] = useState({ items:[] });
const [selectedScript, setSelectedScript] = useState('');
const [selectedScripts, setSelectedScripts] = useState([]);
let { id } = useParams();
const [scriptDetailId] = useState(id);
const [url, setUrl] = useState(`${process.env.REACT_APP_API_URL}/api/script-group/script-group?id=` + scriptDetailId);
function fetchData() {
const fetchItem =fetch(url).then(response => response.json()).then(json => {
setItem(json);
},
(error) => {
console.log(error);
});
const fetchScriptList = fetch(`${process.env.REACT_APP_API_URL}/api/script/scripts`).then(response => response.json()).then(json => {
setScriptList(json);
},
(error) => {
console.log(error);
});
const fetchSelectedScripts = fetch(`${process.env.REACT_APP_API_URL}/api/script-group-member/script-group-members-by-group-id?id=` + scriptDetailId).then(groupResponse => groupResponse.json()).then(groupJson => {
groupJson = groupJson.map((member, i) => ({ ...member, index: i }));
setSelectedScripts(groupJson);
},
(groupError) => {
console.log(groupError);
setSelectedScripts([]);
});
Promise.all([fetchItem, fetchScriptList, fetchSelectedScripts]);
}
function onCancel(e) {
fetchData();
}
function onSubmit(e) {
const requestOptions = {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(item)
};
var scriptList = [];
for (var i = 0; i < selectedScripts.length; i++) {
scriptList.push(selectedScripts[i].scriptId);
}
const updateMembersOptions = {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(scriptList)
}
fetch(`${process.env.REACT_APP_API_URL}/api/script-group?id=` + scriptDetailId, requestOptions).then(
fetch(`${process.env.REACT_APP_API_URL}/api/script-group/replace-group-scripts?id=` + scriptDetailId, updateMembersOptions)
).then(setUrl(url));
}
function getMaxIndex() {
return Math.max.apply(Math, selectedScripts.map(function (o) { return o.index })) + 1;
}
const handleScriptChange = (event) => {
const {
target: { value },
} = event;
var newScriptVal = { ...value, index: getMaxIndex() };
setSelectedScripts([...selectedScripts, newScriptVal])
setSelectedScript('');
};
function removeScript(e) {
if (e.currentTarget.id) {
var index = parseInt(e.currentTarget.id);
setSelectedScripts(selectedScripts.filter((s) => s.index !== index));
}
}
function getBoolStringVal(value) {
if (value)
return "Yes";
else
return "No";
}
useEffect(fetchData, [url, scriptDetailId]);
return (
<div style={{ marginLeft: '10%', maxWidth: '80%' }}>
<TextField
id="groupName"
label="Group Name"
error={validationError}
margin="normal"
fullWidth
value={item.groupName || ''}
onChange={e => setItem({ ...item, groupName: e.target.value })}
/>
<br />
<TextField
aria-readonly
id="groupState"
label="State"
fullWidth
error={validationError}
margin="normal"
value={item.currentStateDescription || ''}
/>
<br />
<TextField
aria-readonly
id="continueOnFailure"
label="Continues on Failure"
error={validationError}
margin="normal"
value={getBoolStringVal(item.continueOnFailure) || ''}
/>
<br />
<FormControl margin="normal">
<InputLabel id="script-select-label" >Add Script</InputLabel>
<Select
labelId="script-select-label"
label="Add Script"
value={selectedScript}
onChange={handleScriptChange}
style={{ width: '350px' }}
>
{
scriptList.items.map((script) => (
<MenuItem
key={script.scriptId}
value={script}
>
{script.scriptName}
</MenuItem>
))}
</Select>
</FormControl>
<br />
<h4>Current Scripts:</h4>
<List dense style={{ width: '300px' }}>
{selectedScripts.map((script) => (
<ListItem key={script.index}>
<ListItemButton>
<ListItemIcon>
<ClearIcon id={script.index} onClick={removeScript} />
</ListItemIcon>
<ListItemText primary={script.scriptName} />
</ListItemButton>
</ListItem>
))}
</List>
<Button variant='outlined' aria-label='Update' style={{ margin: 10, float: 'right' }} onClick={onSubmit}>Update</Button>
<Button variant='outlined' aria-label='Cancel' style={{ margin: 10, float: 'right' }} onClick={onCancel}>Cancel</Button>
<ScriptGroupResultsTable style={{ marginTop: '100px' }} groupid={scriptDetailId} />
</div>
);
}

Search bar, <input/>

Hello everyone :D I need your advise/tip. Right now I have a APIDataTable component. It has its rows, columns and etc. This component is responsible to show/build data table on frontend with search bar in it above the table. I have an search bar, which is not functional right now. I want it to search data from data table. What should I start from? How can i make it perform search in Table. Thank you for any advise and tip <3
import React, { useEffect, useState } from "react";
import { plainToClassFromExist } from "class-transformer";
import { Pagination } from "../../models/Pagination";
import {
DataTable,
DataTableHead,
DataTableHeadCell,
DataTableBody,
DataTableRow,
DataTableCell,
} from "../DataTable";
import { request } from "../../api";
import "./index.css";
import { MenuSurface } from "../MenuSurface";
import { IconButton } from "../IconButton";
import { Checkbox } from "../Checkbox";
import { Dialog } from "../Dialog";
import { GridCell, GridRow } from "../Grid";
import { Button } from "../Button";
export class Column<T> {
label: string;
width?: number;
filter?: JSX.Element;
render: (row: T) => JSX.Element | string;
constructor(column: Partial<Column<T>>) {
Object.assign(this, column);
}
}
type APIDataTableProps<T> = {
apiPath?: string;
params?: string;
columns?: Column<T>[];
type: Function;
onRowClick?: (row: T) => void;
};
export const APIDataTable = <T extends object>({
apiPath,
params,
columns,
type,
onRowClick,
}: APIDataTableProps<T>) => {
const [data, setData] = useState<Pagination<T>>(null);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(15);
const [isLoading, setIsLoading] = useState(false);
const [isDialogOpen, setDialogOpen] = useState(false);
const [isMenuSurFaceOpen, setMenuSurfaceOpen] = useState(false);
const [hiddenColumns, setHiddenColumns] = useState<number[]>(
JSON.parse(localStorage.getItem(`hiddenColumns-${apiPath + params}`)) || []
);
const fetchData = async () => {
const urlSearchParams = new URLSearchParams(params);
urlSearchParams.set("page", page.toString());
urlSearchParams.set("page_size", pageSize.toString());
const url = `${apiPath}?${urlSearchParams}`;
const response = await request(url);
const data = plainToClassFromExist(new Pagination<T>(type), response, {
excludeExtraneousValues: true,
});
setData(data);
setIsLoading(false);
};
useEffect(() => {
if (!!apiPath) {
setIsLoading(true);
fetchData();
}
}, [page, pageSize]);
const headCells = columns
.filter((e, i) => !hiddenColumns?.includes(i))
.map((column) => (
<DataTableHeadCell key={column.label} width={column.width}>
{column.label}
</DataTableHeadCell>
));
const rows = data?.results?.map((row, index) => (
<DataTableRow
key={"row-" + index}
onClick={() => !!onRowClick && onRowClick(row)}
>
{columns
.filter((e, i) => !hiddenColumns?.includes(i))
.map((column) => {
return (
<DataTableCell key={column.label} width={column.width}>
<div className="data-table-cell-text">{column.render(row)}</div>
</DataTableCell>
);
})}
</DataTableRow>
));
let uncheckedCheckboxes = hiddenColumns;
const onCheckboxChange = (index: number, value: boolean) => {
if (!value) {
uncheckedCheckboxes.push(index);
//setHiddenColumns(uncheckedCheckboxes);
} else {
const array = [...uncheckedCheckboxes];
array.splice(array.indexOf(index), 1);
uncheckedCheckboxes = array;
}
};
const [isOpen, setIsOpen] = useState(false);
return (
<div className="data-table-container">
<div className="search-test">
<div className="mdc-menu-surface--anchor">
<label
className="mdc-text-field mdc-text-field--filled mdc-text-field--no-label mdc-text-field--with-leading-icon mdc-text-field--with-trailing-icon"
htmlFor="input"
id="search-menu-surface"
>
<IconButton density={-1} icon="search" />
<input
className="mdc-text-field__input "
type="text"
placeholder="Поиск"
id="searchinput"
/>
<IconButton
density={-1}
icon="arrow_drop_down"
onClick={() => {
setMenuSurfaceOpen(true);
}}
/>
</label>
<MenuSurface
isOpen={isMenuSurFaceOpen}
onClose={() => setMenuSurfaceOpen(false)}
fullwidth
>
<div className="data-table-filters-container">
{columns.map(
(column) =>
!!column.filter && (
<div className="data-table-filter">
<div className="data-table-filter-label mdc-typography--subtitle1">
{column.label}
</div>
<div className="data-table-column-filter">
{column.filter}
</div>
</div>
// <GridRow>
// <GridCell span={3}>{column.label}</GridCell>
// <GridCell span={3}>{column.filter}</GridCell>
// </GridRow>
)
)}
{/* <GridCell span={2}> */}
{/* <Button label="Поиск" raised /> */}
{/* <Button
label="Отмена"
raised
onClick={() => {
setIsOpen(false);
}}
/> */}
{/* </GridCell> */}
</div>
</MenuSurface>
</div>
<IconButton
onClick={() => {
setDialogOpen(true);
}}
density={-1}
icon="settings"
/>
<Dialog
isOpen={isDialogOpen}
onOkClick={() => {
localStorage.setItem(
`hiddenColumns-${apiPath + params}`,
JSON.stringify(uncheckedCheckboxes)
);
setDialogOpen(false);
setHiddenColumns(uncheckedCheckboxes);
}}
onCloseClick={() => setDialogOpen(false)}
>
<div
style={{
display: "flex",
flexDirection: "column",
}}
>
{columns.map((column, index) => (
<Checkbox
label={column.label}
onChange={(value) => onCheckboxChange(index, value)}
defaultChecked={!uncheckedCheckboxes.includes(index)}
/>
))}
</div>
</Dialog>
</div>
<DataTable
pagination={true}
count={data?.count}
rowsNumber={data?.results.length}
page={page}
next={data?.next}
previous={data?.previous}
isLoading={isLoading}
onNextClick={() => setPage(page + 1)}
onPreviosClick={() => setPage(page - 1)}
>
<DataTableHead>{headCells}</DataTableHead>
<DataTableBody>{rows}</DataTableBody>
</DataTable>
</div>
);
};
I'm guessing that you want to search bar to effectively filter out rows that don't match. in this case what you want to do is add a filter to the search text (naturally you'll add a state for the search value, but it looks like you'll have that handled.
You'll add your filter here const rows = data?.results?.filter(...).map
You filter function will look something like this
const rows = data?.results.filter((row) => {
// In my own code if I have other filters I just make them return false
// if they don't match
if (
searchText &&
!(
// exact match example
row.field === searchText ||
// case-insensitive example
row.otherField?.toLowerCase().includes(searchText)
// can continue with '||' and matching any other field you want to search by
)
)
return false;
return true;
}).map(...)

Resources