React table row selection - reactjs

I've followed below link and tried to construct a table grid which includes row selection.
https://codesandbox.io/embed/github/tannerlinsley/react-table/tree/master/examples/row-selection
But for some reason it's not working. It's allowing me to select any two rows. When I select 3rd row the previous selection isn't remembered.
Here is the code snippet.
import React, { useState, useEffect, useRef } from "react";
import { useQuery } from "#apollo/react-hooks";
import { ContentWrapper } from "#nextaction/components";
import { useTable, useRowSelect, usePagination } from "react-table";
import $ from "jquery";
import { Row, Col, Button, Badge } from "reactstrap";
import FETCH_XXXXX_QUERY from "../queries/FetchXXXXXQuery";
import { Link } from "react-router-dom";
const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, ...rest }, ref) => {
const defaultRef = React.useRef();
const resolvedRef = ref || defaultRef;
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
}, [resolvedRef, indeterminate]);
return (
<>
<input type="checkbox" ref={resolvedRef} {...rest} />
</>
);
}
);
function XxxxxxGrid({ columns, data, prospectData }) {
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
selectedFlatRows,
state: { selectedRowIds },
} = useTable(
{
columns,
data,
prospectData,
},
useRowSelect,
(hooks) => {
hooks.visibleColumns.push((columns) => [
// Let's make a column for selection
{
id: "selection",
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox
Header: ({ getToggleAllRowsSelectedProps }) => (
<div>
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
),
},
...columns,
]);
}
);
// Render the UI for your table
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.slice(0, 10).map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
})}
</tr>
);
})}
</tbody>
</table>
);
}
const Xxxxxxxx = ({ actions, history }) => {
const { data, loading, error } = useQuery(QUERY);
const gridData = React.useMemo(() => (data ? data.xxxxx.data : []), [data]);
const [activeTab, setActiveTab] = useState("ALL");
const columns = React.useMemo(
() => [
{
Header: "Name",
accessor: "contactName",
Cell: function (props) {
return (
<span>
<b>
<Link to={"/xxxxx/" + props.row.original.id}>
{props.value}
</Link>
</b>
<br></br>
<span>{props.row.original.title}</span>
<br></br>
<Link to={"/xxxxx/" + props.row.original.id}>
{props.row.original.accountName}
</Link>
</span>
);
},
},
{
Header: "Cadence",
accessor: "accountName",
Cell: function (props) {
return (
<span>
<b>
<Link to={"/xxxxxxxx/" + cadence.id}>{props.value}</Link>
</b>
</span>
);
},
},
{
Header: "Tags",
accessor: "tag",
Cell: function (props) {
return (
<Badge color="secondary" pill>
{props.value}
</Badge>
);
},
},
{
Header: "C Outcome",
accessor: "phone",
},
{
Header: "E Outcome",
accessor: "title",
},
{
Header: "Last Contact",
accessor: "createdDate",
},
],
[]
);
const XxxxxxGrid = React.useMemo(
() => (
<XxxxxxGrid
columns={columns}
data={gridData}
prospectData={prospectData}
/>
),
[gridData]
);
return (
<ContentWrapper>
<div className="content-heading pb-0">
<div>
<em className="fa fa-user fa-lg"></em> Prospects
</div>
<div className="ml-auto">
<ul className="nav nav-tabs">
<li className="nav-item">
<a
href="#"
className={
"nav-link text-center" +
(activeTab === "xxxx" ? " active" : "")
}
>
<h4 className="text-primary">1522</h4>
<h6>xxx</h6>
</a>
</li>
<li className="nav-item">
<a
href="#"
className={
"nav-link text-center" +
(activeTab === "xxxx" ? " active" : "")
}
>
<h4 className="text-primary">1522</h4>
<h6>xxxx</h6>
</a>
</li>
<li className="nav-item">
<a
href="#"
className={
"nav-link text-center" +
(activeTab === "xxxx" ? " active" : "")
}
>
<h4 className="text-primary">1522</h4>
<h6>xxx</h6>
</a>
</li>
<li className="nav-item">
<a
href="#"
className={
"nav-link text-center" +
(activeTab === "xxxxxx" ? " active" : "")
}
>
<h4 className="text-primary">1522</h4>
<h6>xxxxx</h6>
</a>
</li>
</ul>
</div>
</div>
<Row className="mb-3">
<Col sm={4}>
<div className="input-group pl-0">
<input
type="text"
className="form-control"
placeholder="Search"
aria-label="Search"
aria-describedby="button-addon2"
/>
<div className="input-group-append">
<Button
color="outline-secondary"
type="button"
id="button-addon2"
>
<i className="fa fa-search"></i>
</Button>
</div>
</div>
</Col>
</Row>
<Row className="border-bottom border-top">
<div className="ml-auto">
<Button color="outline">
<em className="fas fa-file-csv text-primary"></em> Import CSV
</Button>
|
<Button color="outline">
<em className="fa fa-plus text-primary"></em> Add
</Button>
</div>
</Row>
<Row>{!loading && XxxxxxGrid}</Row>
</ContentWrapper>
);
};
export default Xxxxxxxx;

Something similar happened to me,
in my case the bug was generated due to a conflict with the StrictMode generated by default in index.js.
Try to Delete it
ReactDOM.render (
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById ('root')
the Index should look like this:
ReactDOM.render (
<App />,
document.getElementById ('root')

Related

How can I maintain the order of adding items in react web app?

How can I create an order of adding items. I should be able to add one after the other item during on click. By default it should display the Textarea first and then blockquote ( see below )
a) When a user click on Text area button, it should add one after the blockquote.
b) Then when the user clicks on Code area button, it should add after Textarea. Could someone please advise ?
CSB link: https://codesandbox.io/s/distracted-worker-26jztf?file=/src/App.js
Something similar >> Expected behaviour: https://jsfiddle.net/nve8qdbu/8/
import "./styles.css";
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form";
const blogListData = [
{
id: 1,
heading: "React state",
date: "22-May-2022",
tag: "React",
count: "3"
},
{
id: 2,
heading: "Cypress testing detailss",
date: "22-May-2022",
tag: "Cypress",
count: "5"
}
];
const Admin = () => {
const [createImageTag, setImageTag] = useState("");
const [fields, setFields] = useState([{ value: null }]);
const [createCode, setCreateCode] = useState([{ value: null }]);
const [blogList, setBlogList] = useState([]);
const {
register,
handleSubmit,
formState: { errors },
reset
} = useForm();
useEffect(() => {
setBlogList(blogListData);
}, []);
function handleChangeTextArea(i, event) {
const values = [...fields];
values[i].value = event.target.value;
setFields(values);
}
function handleChangeCode(i, event) {
const codeValues = [...createCode];
codeValues[i].value = event.currentTarget.innerText;
setCreateCode(codeValues);
}
function handleTextAreaAdd() {
const values = [...fields];
values.push({ value: null });
setFields(values);
}
function handleCodeAreaAdd() {
const codeValues = [...createCode];
codeValues.push({ value: null });
setCreateCode(codeValues);
}
function handleImageAreaAdd() {
const image = [...createImageTag];
image.push({ value: null });
setCreateCode(image);
}
function handleRemoveText(i) {
const values = [...fields];
values.splice(i, 1);
setFields(values);
}
function handleRemoveCode(i) {
const codeValues = [...createCode];
codeValues.splice(i, 1);
setCreateCode(codeValues);
}
const handleLogout = () => {
localStorage.removeItem("loginEmail");
};
return (
<div id="App">
<div className="parent">
<div className="adminSection">
<h1>Create a new blog</h1>
<div className="row">
<div className="logout">
<img
src="/images/logout.png"
alt="Logout"
onClick={handleLogout}
></img>
</div>
<div className="createBlogSection">
<div className="row">
<button
onClick={() => handleTextAreaAdd()}
className="textAreaBtn"
>
Text Area
</button>
<button
onClick={() => handleCodeAreaAdd()}
className="codeAreaBtn"
>
Code Area
</button>
<button
onClick={() => handleImageAreaAdd()}
className="imageAreaBtn"
>
Add Image
</button>
</div>{" "}
<br></br>
<div className="row">
{fields.map((field, idx) => {
return (
<div key={`${field}-${idx}`} className="dtextArea">
<button
type="button"
onClick={() => handleRemoveText(idx)}
className="closeElement"
>
X
</button>
<textarea
type="text"
id="blogtext"
placeholder="Enter your text here"
className="defaultTextArea"
{...register("blogtext", {
required: true,
minLength: {
value: 25,
message: "Minimum length of 25 letters"
}
})}
value={field.value || ""}
onChange={(e) => handleChangeTextArea(idx, e)}
/>
<span className="validationmsg">
{errors.blogtext &&
errors.blogtext.type === "required" && (
<span>Blog text is required !</span>
)}
{errors.blogtext && (
<span>{errors.blogtext.message}</span>
)}
</span>
</div>
);
})}
</div>
<div className="row">
{createCode.map((code, idx) => {
return (
<div key={`${code}-${idx}`} className="dCodeArea">
<button
type="button"
onClick={() => handleRemoveCode(idx)}
className="closeElement"
>
X
</button>
<blockquote
type="text"
id="blogCode"
contentEditable="true"
className="codehighlight"
placeholder="Enter your code here"
{...register("blogCode", {
required: true
})}
value={code.value || ""}
onInput={(e) => handleChangeCode(idx, e)}
/>
</div>
);
})}
</div>
<div className="row">
</div>
<div className="row">
<div className="submitSection">
<input type="submit" className="submitBtn" />
</div>
</div>
</div>
</div>
</div>
<div className="blogListSection">
<h1>Edit blogs</h1>
<div className="row">
<div className="editBlogSection">
{blogList.map(({ id, heading, count }) => (
<a
key={id}
href="https://www.testingsite.com/"
className="blogitems"
>
<pre>
<span>{count}</span> {heading}
</pre>
</a>
))}
</div>
</div>
</div>
</div>
</div>
);
};
export default Admin;
react is designed for components . each of your list elements should be refactored by a component.then it would be easier. i think a single react component could do the trick

All my sidebar menu opens at once in ReactJS

I am trying to create a sidebar menu with dropdwon
For I single menu it works fine but if I have multiple dropdown
All opens at one when I click on the menu to open the submenu.
I am getting the data for the menu from a json array
const SidebarItems = ({ items }) => {
const [open, setOpen] = useState(false)
return (
<div>
{items && items.map((item, index) => {
return (
<div key={index}>
<div>
<li >
<div className="nav-items" onClick={() => setOpen(!open)}>
<span>
<i className="bi-badge-vr" />
{item.title}
</span>
<i className="bi-chevron-down" />
</div>
</li>
</div>
<div className={open ? 'sub-menu sub-menu-open' : "sub-menu"} >
{item.chidren && item.chidren.map((sub_menu, sub_index) => {
return (
<div key={sub_menu}>
<Link to="manager/staff/create">
<span>
<i className="bi-badge-vr" />
{sub_menu.title}
</span>
</Link>
</div>
)
})}
</div>
</div>
)
})}
</div>
)
}
This is because they all use the same state.
Move the item to its own component so I can keep its own state:
const Item = ({ item }) => {
const [open, setOpen] = useState(false);
return <Your item code here />;
};
And in SideBarItems:
items.map((item, index) => {
return <Item item={item} key={index} />;
});

Click event with React Table 7

This is my Cell data in column:
import { format } from "date-fns";
export const COLUMNS_ALLLOAN = [
{
Header: "Id",
accessor: "id",
Cell: ({ row }) => Number(row.id) + 1,
},
{
Header: "Account Number",
accessor: "acNumber",
Cell: ({ row }) => <>{row.original.acNumber}</>,
},
{
Header: "Account Name",
accessor: "acName",
},
{
Header: "Loan Type",
accessor: "acType",
Cell: ({ row }) => <>{row.original.acType}</>,
},
{
Header: "Opening Date",
accessor: "acOpen",
Cell: ({ row }) => <>{row.original.acOpen}</>,
},
{
Header: "Sanction Date",
accessor: "acSanction",
Cell: ({ row }) => <>{row.original.acSanction}</>,
},
{
Header: "Expiry Date",
accessor: "acExpiry",
Cell: ({ row }) => <>{row.original.acExpiry}</>,
},
{
Header: "Limit",
accessor: "acLimit",
Cell: ({ row }) => <>{Number(row.original.acLimit)}</>,
},
{
Header: "Outstanding",
accessor: "lastDayBalance",
Cell: ({ row }) => <>{Number(row.original.lastDayBalance)}</>,
},
{
Header: "Overlimit",
accessor: "overlimit",
Cell: ({ row }) => <>{Number(row.original.overLimit)}</>,
},
{
Header: "Available",
accessor: "available",
Cell: ({ row }) => <>{Number(row.original.availableBalance)}</>,
},
{
Header: "Edit",
accessor: "edit",
Cell: ({ row }) => <button className="btn btn-primary">Edit</button>,
},
{
Header: "Delete",
accessor: "X",
Cell: ({ row }) => <button className="btn btn-danger">X</button>,
},
];
This is my page
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import Footer from "../../components/Footer";
import { useMemo } from "react";
import { useTable, useSortBy, useGlobalFilter, usePagination } from "react-table";
import { COLUMNS_ALLLOAN } from "../../components/COLUMNS_ALLLOAN.js";
import Globalfilter from "../../components/Globalfilter";
import { deleteById, getLoans } from "../../features/bradvance/advanceSlice";
const Allloans = () => {
const dispatch = useDispatch();
const { allLoans } = useSelector((state) => state.bradvance);
const navigate = useNavigate();
const goBack = async () => {
navigate(-1);
};
const columns = useMemo(() => COLUMNS_ALLLOAN, []);
const data = useMemo(() => allLoans, []);
// console.log(data);
const tableInstance = useTable(
{
columns,
data: allLoans,
initialState: { pageSize: 30 },
},
useGlobalFilter,
useSortBy,
usePagination
);
const {
headerGroups,
getTableProps,
getTableBodyProps,
page,
prepareRow,
state,
setGlobalFilter,
nextPage,
previousPage,
canNextPage,
canPreviousPage,
pageOptions,
setPageSize,
gotoPage,
pageCount,
} = tableInstance;
const { globalFilter, pageIndex, pageSize } = state;
// console.log(data.length);
const totalLoan = allLoans
.map((item, sl) => {
return item.lastDayBalance;
})
.reduce((acc, curValue) => {
// console.log(curValue);
return acc + curValue;
}, 0);
// console.log(totalLoan);
const handleClick = (id) => {
dispatch(deleteById(id));
alert("Deleted Successfully.");
console.log(id);
};
return (
<>
<div className="container">
<div className="row">
<div className="col">
<div className="summary">
<h1 className="p-4 text-center fw-bold mb-0">All Loans</h1>
</div>
</div>
</div>
<div className="row">
<div className="col">
<Globalfilter filter={globalFilter} setFilter={setGlobalFilter} />
</div>
</div>
<div className="row">
<div className="col">
<div className="summary table-responsive">
<table {...getTableProps()} className="table table-hover table-bordered">
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>{column.render("Header")} </th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td onClick={() => handleClick(row.original.id)} {...cell.getCellProps()}>
{cell.render("Cell")}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
<div className="row ">
<div className="col d-flex justify-content-end">
<ul className="list-group list-group-horizontal">
<li className="list-group-item fw-bold">
Total Loan: <span className="fw-bold text-danger">{allLoans.length}</span>
</li>
<li className="list-group-item fw-bold">
Total Amount:<span className="fw-bold text-danger">{totalLoan.toFixed(2)}</span>{" "}
</li>
</ul>
</div>
</div>
<div className="section p-5 pagination col-12">
<button className="page-link" onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{"<<"}
</button>{" "}
<button className="page-link" onClick={() => previousPage()} disabled={!canPreviousPage}>
{"<"}
</button>{" "}
<button className="page-link" onClick={() => nextPage()} disabled={!canNextPage}>
{">"}
</button>{" "}
<button className="page-link" onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{">>"}
</button>{" "}
<span className="page-link">
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{" "}
</span>
<span className="page-link">
| Go to page:{" "}
<input
type="number"
value={pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
gotoPage(page);
}}
style={{ width: "100px" }}
/>
</span>{" "}
<select
className="page-link"
value={pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value));
}}
>
{[20, 50, 100, 200, 500].map((pageSize) => (
<option key={pageSize} value={pageSize} className="page-link">
Show {pageSize}
</option>
))}
</select>
</div>
{/* Go Back */}
<div className="row ">
<div className="col ">
<div className="text-center summary pb-3">
<button className="btn btn-outline-primary mb-0" onClick={goBack}>
<i className="bi bi-skip-backward"></i> Go Back
</button>
</div>
</div>
</div>
<div className="row">
<div className="col">
<Footer />
</div>
</div>
</div>
</>
);
};
export default Allloans;
This is how my page looks:
I can delete each row successfully. The problem is that it deletes when I click on any part of the row. But, I want only the button to be clickable not the whole row. I mean, when I click the delete button only, it should delete.
First, remove the onClick={() => handleClick(row.original.id)} from your row td tag so it become as follows
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>
{cell.render("Cell")}
</td>
);
})}
next, move your on-click handler to the top of the table instance creation
...
const handleClick = (id) => {
dispatch(deleteById(id));
alert("Deleted Successfully.");
console.log(id);
};
const tableInstance = useTable(
{
...
}
);
then, remove the last two columns' definitions (Edit & Delete def) from COLUMNS_ALLLOAN and insert those col definitions into useTable and finally add the onClick event listener to the delete button.
const tableInstance = useTable(
{
columns,
data: allLoans,
initialState: { pageSize: 30 },
},
useGlobalFilter,
useSortBy,
usePagination,
// create Edit and Delete col definition by pushing the two into visibleColumns
(hooks) => {
hooks.visibleColumns.push((col) => [
...col,
{
Header: "Edit",
id: "editBtn",
Cell: ({ row }) => <button className="btn btn-primary">Edit</button>,
},
{
Header: "Delete",
id: "deleteBtn",
Cell: ({ row }) => <button onClick={() => handleClick(row.original.id)} className="btn btn-danger">X</button>,
},
]);
}
);
Here is the minimal example:

How to add data into an array using inputs in reactjs?

I am new to react and I am trying to build a todo app which basically can execute the CRUD operation. I am able to update and delete as of now. But not sure how to create an object and save it into the array.
When i click on edit or add task button a modal pop up and i am trying to enter the title and description value there.
This is my Index.js file
import React, {Component} from 'react';
import { Button, Modal } from 'reactstrap';
import Modals from './Modals'
const todoItems = [
{
id: 1,
title: "Go to Market",
description: "Buy ingredients to prepare dinner",
completed: true
},
{
id: 2,
title: "Study",
description: "Read Algebra and History textbook for upcoming test",
completed: false
},
{
id: 3,
title: "Sally's books",
description: "Go to library to rent sally's books",
completed: true
},
{
id: 4,
title: "Article",
description: "Write article on how to use django with react",
completed: false
}
];
class Index extends Component {
state = {
modal: false,
items: todoItems,
selectedItem: null,
selectedIndex: -1,
}
toggle = (item, index) => {
if (item) {
this.setState({ selectedItem: item, selectedIndex: index })
}
this.setState({ modal: !this.state.modal });
};
handleChange = () => {
let oldState = this.state.items;
this.setState({ items: oldState })
}
onDelete = (item) => {
const newData = this.state.items.filter(i => i.title !== item.title)
this.setState({ items: newData })
}
render() {
return (
<>
<h1 className="p-3">TODO APP</h1>
<div style={{ backgroundColor: "white", padding: "50px", color: "black"}} className="container">
<div className="row">
<button className="btn btn-primary" onClick={() => this.toggle()}>Add task</button>
</div>
<div className="row my-5">
<Button color="danger mr-5">Incomplete</Button>
<Button color="success">Complete</Button>
<Modals index={this.state.selectedIndex} onDelete={this.onDelete} item={this.state.selectedItem} handleChange={this.handleChange} modal={this.state.modal} toggle={this.toggle} />
</div>
<hr/>
{this.state.items.map((item, index) => {
return(
<div key={item.id}>
<div className="row">
<p style={{ textAlign: "left" }}>{item.title}</p>
<Button onClick={() => this.toggle(item, index)} className="mr-0 ml-auto" color="info">Edit</Button>
<Button onClick={() => this.onDelete(item)} className="ml-5" color="warning">Delete</Button>
</div>
<hr/>
</div>
)
})}
</div>
</>
);
}
}
export default Index;
This is my modal.js file
import React, { useState, useEffect } from 'react';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
function Modals(props) {
const {
className,
buttonLabel,
handleChange,
item,
index,
toggle,
} = props;
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
useEffect(() => {
if (item && item.title){
setTitle(item.title)
setDescription(item.description)
}
}, [item])
const setValues = () => {
handleChange({ title: title, description: description });
props.toggle();
push({
})
}
return (
<div>
<Modal isOpen={props.modal} className={className} style={{ color: "black"}}>
<ModalHeader >Todo Item</ModalHeader>
<ModalBody>
<div className="container-fluid">
<div className="row mb-3">
<div className="col-12">
<p className="mb-0">Title</p>
</div>
<div className="col-12">
<input onChange={(e) => setTitle(e.target.value)} value={title} className="w-100" type="text" placeholder="Enter title"/>
</div>
</div>
<div className="row mb-3">
<div className="col-12">
<p className="mb-0">Description</p>
</div>
<div className="col-12">
<input type="text" onChange={(e) => setDescription(e.target.value)} value={description} className="w-100" placeholder="Enter Description"/>
</div>
</div>
<div className="row">
<div className="col-12">
<input type="checkbox"/>
<span className="ml-2"> Completed </span>
</div>
</div>
</div>
</ModalBody>
<ModalFooter>
<Button onClick={() => setValues()} color="success">Submit</Button>
<Button onClick={props.toggle} color="secondary">Cancel</Button>
</ModalFooter>
</Modal>
</div>
)
}
export default Modals;
Thanks in advance!!
One way would be to just create a method in index.js
addItem = (item) =>{
this.setState({items: [...this.state.items, item]})
}
and then just pass it as a prop to your Modal and call it in setValues,
const setValues = () => {
handleChange({ title: title, description: description });
props.toggle();
props.addItem({ title: title, description: description, //etc})
}

Irregular status 500 on Apollo GraphQL requests ("Variable X of required type \"String!\" was not provided.")

I get a network error just some of the times I call a lazyQuery in my React project with Apollo and GraphQL. In my app I can successfully get search results back containing various drinks. These are mapped to their own buttons. On clicking one of these buttons a function triggers that queries data about that specific drink.
Most of the time it works fine, but some of the times I get a status 500 error, saying that the cocktailName variable of the type string was not provided. When I've tried requesting the same drinks several times I've sometimes gotten a good response and sometimes not. When I try to debug the function that sends the query, it seems to receive the variable every single time, and I haven't managed to crash the app when I have debug breakpoints in that function. I'd really appreciate any assistance with this!
Main component for the drinks:
import React from 'react';
import { useStoreState, useStoreActions } from 'easy-peasy';
import { useQuery, useLazyQuery } from '#apollo/react-hooks';
import { RANDOM_COCKTAIL_QUERY, ONE_COCKTAIL_BY_NAME_QUERY } from './Queries';
import LoadingSpinner from './LoadingSpinner';
import CocktailSearchFieldAlt from './CocktailSearchFieldAlt';
import { cocktailIngredientList, cocktailMeasureList } from './ingredientLists';
// Imported ingredient and measure lists represent names of keys in objects
export default function Cocktails() {
const dataType = useStoreState((state) => state.cocktailDataType);
const changeDataType = useStoreActions((actions) => actions.changeCocktailDataType);
const dataSet = useStoreState((state) => state.cocktailDataSet);
const changeDataSet = useStoreActions((actions) => actions.changeCocktailDataSet);
const randomizeClick = () => {
if (dataType !== 'randomCocktail') {
changeDataType('randomCocktail');
changeDataSet('data');
}
refetch();
};
const specifikLookup = (drinkName) => {
if (dataType === 'randomCocktail') {
changeDataSet('specificData');
changeDataType('oneCocktailByName');
}
getDrink({ variables: { cocktailName: drinkName } });
};
const { loading, error, data, refetch } = useQuery(RANDOM_COCKTAIL_QUERY);
const [
getDrink,
{ data: specificData, loading: loadingSpecific, error: errorSpecific }
] = useLazyQuery(ONE_COCKTAIL_BY_NAME_QUERY);
if (loading || loadingSpecific) return <LoadingSpinner />;
if (error) return `Error! ${error}`;
if (errorSpecific) return `Error! ${errorSpecific}`;
return (
<section id="cocktail">
<h2 className="mainHeader mt-5 mb-4 scrollTrick">Cocktail</h2>
<div className="card card-body mb-3">
<div className="row">
<div className="col-md-9">
<h4>{eval(dataSet)[dataType].strDrink}</h4>
<p>
{eval(dataSet)[dataType].strAlcoholic}
<br />
Best served in: {eval(dataSet)[dataType].strGlass}
</p>
<h6>Recipee</h6>
<div className="row">
<div className="col-md-4">
<ul>
{eval(dataSet) &&
cocktailIngredientList.map(
(x, i) =>
eval(dataSet)[dataType][x] && (
<li key={i}>{eval(dataSet)[dataType][x]}</li>
)
)}
</ul>
</div>
<div className="col-md-3">
<ul>
{data &&
cocktailMeasureList.map(
(x) => eval(dataSet)[dataType][x] && <li>{eval(dataSet)[dataType][x]}</li>
)}
</ul>
</div>
</div>
<h6>Instructions</h6>
<p>{eval(dataSet)[dataType].strInstructions}</p>
</div>
<div className="col-md-3">
<img src={eval(dataSet)[dataType].strDrinkThumb} alt="Cocktail" className="img-fluid mb-3" />
<button className="btn btn-primary btn-block" onClick={() => randomizeClick()}>
Randomize Cocktail
</button>
</div>
</div>
{/* <CocktailSearchField specificLookup={specifikLookup} /> */}
<CocktailSearchFieldAlt specificLookup={specifikLookup} />
</div>
</section>
);
}
Search component for the drinks:
import React, { useState } from 'react';
import { useStoreState, useStoreActions } from 'easy-peasy';
import { useLazyQuery } from '#apollo/react-hooks';
import { MULTIPLE_COCKTAILS_BY_NAME_QUERY, MULTIPLE_COCKTAILS_BY_INGREDIENT_QUERY } from './Queries';
import LoadingSpinner from './LoadingSpinner';
export default function SearchField(props) {
const [
searchInput,
setSearchInput
] = useState('');
const drinkOptionValue = useStoreState((state) => state.drinkOptionValue);
const changeDrinkOptionValue = useStoreActions((actions) => actions.changeDrinkOptionValue);
const handleSubmit = (e) => {
e.preventDefault();
getDrinks({ variables: { cocktailName: searchInput } });
getDrinksByIngredient({ variables: { ingredientName: searchInput } });
};
const [
getDrinks,
{ loading, data }
] = useLazyQuery(MULTIPLE_COCKTAILS_BY_NAME_QUERY);
const [
getDrinksByIngredient,
{ loading: loadingByIngredient, data: dataByIngredient }
] = useLazyQuery(MULTIPLE_COCKTAILS_BY_INGREDIENT_QUERY);
const lookupDrink = (e) => {
console.log(e.target.value);
changeDrinkOptionValue(e.target.value);
props.specificLookup(e.target.value);
};
if (loading || loadingByIngredient) return <LoadingSpinner />;
return (
<div>
<div className="row border-top mt-3 justify-content-center">
<div className="mt-3">
<form className="form-inline my-2 my-lg-0" onSubmit={handleSubmit}>
<input
className="form-control mr-sm-1"
type="text"
placeholder="Search cocktail"
onChange={(e) => setSearchInput(e.target.value)}
value={searchInput}
/>
<button className="btn btn-secondary my-2 my-sm-0 pl-3 pt-2 pb-2 pr-3" type="submit">
<i className="material-icons mt-2">search</i>
</button>
</form>
</div>
</div>
{data &&
(data.multipleCocktailsByName !== null ? (
<div className="container">
<div className="row">
<div className="col">
<h6 className="mt-3 mb-3 text-center">Search results: </h6>
</div>
</div>
<div className="row justify-content-center">
{dataByIngredient.multipleCocktailsByIngredient !== null &&
dataByIngredient.multipleCocktailsByIngredient.map((cocktailByIngredient) => {
if (
data.multipleCocktailsByName.some(
(cocktail) => cocktail.strDrink === cocktailByIngredient.strDrink
)
) {
return;
} else {
data.multipleCocktailsByName.push(cocktailByIngredient);
}
})}
{
(data.multipleCocktailsByName.sort(
(a, b) => (a.strDrink > b.strDrink ? 1 : b.strDrink > a.strDrink ? -1 : 0)
),
data.multipleCocktailsByName.map((cocktail, i) => (
<button
className="btn btn-outline-secondary p-1 menuButton btnAnimated mr-1 mb-1 border border-secondary"
key={i}
value={cocktail.strDrink}
onClick={lookupDrink}
>
<img src={cocktail.strDrinkThumb} className="menuPicture" alt="cocktail" />
{cocktail.strDrink}
</button>
)))
}
</div>
</div>
) : dataByIngredient.multipleCocktailsByIngredient !== null ? (
<div className="container">
<div className="row">
<div className="col">
<h6 className="mt-3 mb-3 text-center">Search results: </h6>
</div>
</div>
<div className="row justify-content-center">
{dataByIngredient.multipleCocktailsByIngredient.map((cocktail, i) => (
<button
className="btn btn-outline-secondary p-1 menuButton btnAnimated mr-1 mb-1 border border-secondary"
key={i}
value={cocktail.strDrink}
onClick={lookupDrink}
>
<img src={cocktail.strDrinkThumb} className="menuPicture" alt="cocktail" />
{cocktail.strDrink}
</button>
))}
</div>
</div>
) : (
<div className="row justify-content-center">
<p>No matching result</p>
</div>
))}
</div>
);
}

Resources