I'm using apollo client in my Reactjs application along with Redux. I'm fetching client details from mongodb by authenticating via JWT token. For this I have used useLazy Query. When user starts typing in the autocomplete box, There would be an authetication going on behind the screen and fetches the client details once the authentication is successful. However, I have a filter condition ,in which, I want to load the countries without any event so I used useQuery. But the results are not fetching in the beginning itself. It works when I use "useQuery" as well as "useLazyQuery" for the same (FIND_COUNTRY)
import * as React from "react"
import TextField from "#mui/material/TextField"
import Autocomplete from "#mui/material/Autocomplete"
import parse from "autosuggest-highlight/parse"
import match from "autosuggest-highlight/match"
import { FIND_CLIENT_NAME, FIND_COUNTRY,CLIENT_AUTOCOMPLETE } from "../graphql-operations"
import { useLazyQuery, useQuery } from "#apollo/client"
import Button from "#mui/material/Button"
import SearchIcon from "#mui/icons-material/Search"
import FilterAltTwoToneIcon from "#mui/icons-material/FilterAltTwoTone"
import { Grid, Stack } from "#mui/material"
import { Container } from "#mui/system"
import AccordionDetails from "#mui/material/AccordionDetails"
import Chip from "#mui/material/Chip"
import { useDispatch } from "react-redux"
import { useMsal } from "#azure/msal-react"
import { loginRequest } from "../authConfig"
import { fetchUsersToken } from "../userSlice"
import { useEffect } from "react"
import ClientListing from "./ClientListing"
import { store } from "../store"
import { useState } from "react"
//Country Filter ------------------------------------------
function Filter(country, setCountry, countryArray, setSearchResults) {
return (
<center>
<AccordionDetails sx={{ maxWidth: 800, height: "auto", minWidth: 240 }}>
<div>
<Autocomplete
data-testid="filter"
size="small"
multiple
id="fixed-tags-demo"
freeSolo
value={country}
onChange={(event, newValue) => {
setCountry([...newValue])
setSearchResults(false)
}}
options={countryArray}
getOptionLabel={option => option.country}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip
size="small"
key={option.countryCode}
label={option.country}
{...getTagProps({ index })}
/>
))
}
style={{ maxwidth: 350, margin: 0, minWidth: 80 }}
renderInput={params => (
<TextField
data-testid="filter-text"
{...params}
label="Country"
variant="filled"
placeholder="Add Country"
/>
)}
/>
</div>
</AccordionDetails>
</center>
)
}
//------------------------------------------------------
export default function LandingPage() {
const [clients, setClients] = React.useState([]) //stores data for autocomplete search from useLazyQuery.
const [filterFlag, setFilterFlag] = React.useState(false)// flag for filter open/close.
const [inputValue, setInputValue] = React.useState("")// stores input value for client search autocomplete.
const [country, setCountry] = React.useState([])//stores country chosen by user.
const [searchResults, setSearchResults] = React.useState(false)//Flag to enable submit after autocomplete onChange complete.
const [SearchInput, setSearchInput] = React.useState()//setting client search value for pagination on submit.
const [SearchCountry, setSearchCountry] = React.useState([])//setting country filter search value for pagination on submit.
const [renderPagination, setRenderPagination] = React.useState(false)//Flag to render pagination table and disable banner image.
//useQuery to load country values for filter country autocomplete----------------------------------------
var countryArray = []
var sorted = []
const { loading: loadingCountry, data: countryData } = useQuery(FIND_COUNTRY)
if (loadingCountry) <p>Loading..</p>
if (countryData != undefined) {
sorted = [...countryData.getCountries.data].sort((a, b) =>
{
if(a.country !== null)
return a.country.localeCompare(b.country)}
)
sorted.map(countryData => {
if (countryData.country) countryArray.push(countryData)
})
console.log("Fetched Countried from UseQuery \n ", countryData);
}
//Redux begins here to acquire token from reducer
const { accounts } = useMsal();
const request = {
...loginRequest,
account: accounts[0]
}
store.dispatch(fetchUsersToken(request))
//Redux ends here
//useLazyQuery to load client Name values for client search autocomplete----------------------------------------
var tempStorage = []
const [clientSearch, { data, loading: loadingQuery }] = useLazyQuery(CLIENT_AUTOCOMPLETE)
async function handleSearch(inputValue) {
//Dispatch action to get access token and store it in RTK (Redux-ToolKit)
try {
if (inputValue) {
const input = inputValue
const clientData = await clientSearch({
variables: { input },
})
clientData.data.autocompleteClient.data.map(({ name }) => {
tempStorage.push({ name })
})
setClients([...tempStorage])
}
} catch (error) {
console.log(error)
}
}
const [getCountryData, { loading, dataTask }] = useLazyQuery(FIND_COUNTRY);
async function filterOpenClose() {
getCountryData().then((fetchedData) => {
sorted = [...fetchedData.data.getCountries.data].sort((a, b) =>
{
if(a.country !== null)
return a.country.localeCompare(b.country)}
)
sorted.map(countryData => {
if (countryData.country) countryArray.push(countryData)
})
})
setFilterFlag(!filterFlag)
setSearchResults(false)
}
const handleSubmit = () => {
if (inputValue) {
setSearchResults(true)
setRenderPagination(true)
setSearchInput(inputValue)
setSearchCountry(country)
}
}
return (
<>
{!renderPagination && <div className="banner-image" role="image" />}
<div>
<br />
<center>
<Container>
<Grid
container
spacing={2}
direction="row"
justifyContent="center"
alignItems="center"
>
<Grid item xs={12} md={12} lg={9}>
<Autocomplete
data-testid="Autocomplete-search"
size="small"
clearOnBlur={false}
id="highlights-demo"
options={clients}
getOptionLabel={option => option.name}
renderInput={params => (
<TextField
sx={{ maxWidth: 775, marginRight: 0 }}
{...params}
placeholder="Search by Client Name/ Client ID /DUNS Number"
margin="normal"
onChange={event => {
handleSearch(event.target.value)
setInputValue(event.target.value)
setSearchResults(false)
}}
onSelect={val => {
setInputValue(val.target.value)
}}
/>
)}
renderOption={(props, option, { inputValue }) => {
const matches = match(option.name, inputValue, {
insideWords: true,
})
const parts = parse(option.name, matches)
return (
<li {...props}>
<div>
{parts.map((part, index) => (
<span
key={index}
style={{
fontWeight: part.highlight ? 700 : 400,
}}
>
{part.text}
</span>
))}
</div>
</li>
)
}}
/>
</Grid>
<Grid item xs={12} md={6} lg={3}>
<Stack direction="row" spacing={1}>
<Button
data-testid="button"
variant="contained"
startIcon={<SearchIcon />}
className="primary-btn"
onClick={handleSubmit}
>
Search
</Button>
<Button
data-testid="filter-icon"
variant="contained"
startIcon={<FilterAltTwoToneIcon />}
className="primary-btn"
onClick={filterOpenClose}
>
Filter
</Button>
</Stack>
</Grid>
</Grid>
</Container>
</center>
{/* renders filter component */}
{filterFlag && Filter(country, setCountry, countryArray, setSearchResults)}
{/* renders pagination table component */}
<br></br>
{renderPagination && (
<ClientListing
clientNameInput={SearchInput}
country={SearchCountry}
searchResults={searchResults}
/>
)}
</div>
<br></br>
</>
)
}
graphql_operations.js
import {gql} from '#apollo/client'
export const CLIENT_AUTOCOMPLETE = gql`
query clientAutocomplete($input: String) {
autocompleteClient(input: $input){
data{
name
}
Error{
Message
}
}
}
`;
export const FIND_COUNTRY = gql`
query {
getCountries{
data{
country
countryCode
}
Error{
Message
}
}
}
`;
export const FIND_CLIENT_NAME = gql`
query getclients($input: SearchQuery) {
searchClients(input: $input){
clients{
id
name
dunsNumberCode
dunsNameDesc
ultimateDunsNumber
ultimateDunsDesc
nationalId
clientAddress{
countryName
countryCode
addressLine1Desc
addressLine2Desc
cityName
postalCode
}
clientIndustry{
industrySectorName
}
}
totalDocsReturned
}
}
`;
export const FIND_CLIENT_ENGAGEMENT = gql`
query getclients($input: SearchQuery) {
searchClients(input: $input){
clients{
id
name
dunsNumberCode
dunsNameDesc
ultimateDunsNumber
ultimateDunsDesc
nationalId
clientAddress{
countryName
countryCode
addressLine1Desc
addressLine2Desc
cityName
postalCode
}
clientIndustry{
industrySectorName
}
engagements{
data{
id
name
clientId
clientName
engagementStatusCode
companyName
companyCode
}
Error{
Message}
}
}
totalDocsReturned
Error{
Message
}
}
}
`;
In the filterOpenClose method, I have useLazyQuery for FIND_COUNTRY and in the beginnning of the component I have used useQuery. I'm getting the country details if and only if I keep both the snippet on.If I remove useLazyQuery for FIND_COUNTRY its not giving me the results. Its undefined. Because of redux, this problem occurs?
Related
I am working with react table where I am passing the data as prop to react table component which is a mobx state. Also I am using the pagination in for react table data. The issue is when I am updating the state value from the cell edit of react table the state is get updated and when if move to the next pagination and do some changes there and comeback to the first page and it shows only the old state value only. I have included my coding below. Can anyone please give me a solution?
Parent component where I am calling the React Table as child component.
`/** #format */
/* eslint-disable prefer-const */
/* eslint-disable #typescript-eslint/naming-convention */
import { toJS } from "mobx";
import { observer } from "mobx-react-lite";
import React, { useEffect, useState } from "react";
import { Button, Card, CardBody, Row } from "reactstrap";
import styled from "styled-components";
// eslint-disable-next-line import/no-extraneous-dependencies
import { useLocation } from "react-router";
import Rocket from "../../assets/img/raw/rocket-pngrepo-com.png";
import ReactTable from "../ReactTable";
import CategorySelector from "./CategorySelector";
import useStore from "../../mobx/UseStore";
import UserService from "../../services/UserService";
import NotificationManager from "../common/react-notifications/NotificationManager";
import IntlMessages from "../../helpers/IntlMessages";
import { REFACTORDATA } from "../../constants/refactorData";
import "./animation.scss";
import { START } from "../../constants";
import LogService from "../../services/LogService";
const CardLayout = styled(Card)`
/* border: 1px solid red; */
/* width: 50%; */
`;
const SaveButton = styled(Button)`
/* position: absolute; */
justify-content: end;
padding: 0.5rem 2rem 0.5rem 2rem;
span {
font-size: 2rem;
}
`;
// const TextArea = styled.textarea`
// /* background-color: #151a30; */
// color: #ffffff;
// `;
const OutPutViewSection = () => {
const { UIState, UserState } = useStore();
// const processedData = REFACTORDATA(UIState.processedData);
// finding current screen
function getCurrentScreenName() {
// eslint-disable-next-line react-hooks/rules-of-hooks
const location = useLocation();
//
const { pathname } = location;
const urlParts = pathname.split("/");
const lastPart = urlParts[urlParts.length - 1];
return lastPart;
}
const currentScreen = getCurrentScreenName();
// bring metadata id for updating database
const { metaDataId, metaData } = UIState;
// statelocal for controlling the category and set data origin
const [dataOrigin, setDataOrigin] = useState(metaData ? metaData.dataOrigin : "");
const [isSelectModalOpen, setIsSelectModalOpen] = useState(false);
const toggleSelectModal = () => {
setIsSelectModalOpen(!isSelectModalOpen);
};
// handling change in dataorigin
const onChangeDataOrigin = ({ target }) => {
const { value } = target;
setDataOrigin(value);
};
// table columns
const onSaveData = async () => {
if (!dataOrigin) {
NotificationManager.info("plase-add-data-origin", null, null, null, null, null);
return;
}
let response;
let uType;
let reqData;
if (currentScreen === "manageCVStatus" || currentScreen === "cvStatus") {
// eslint-disable-next-line #typescript-eslint/no-shadow
const { orgId, adminId, templateId, docPath, processType, totalPages } = metaData;
// while updating from metadata userId is populated
let { userId, _id } = metaData;
// eslint-disable-next-line no-underscore-dangle
userId = userId._id;
reqData = {
orgId,
adminId,
userId,
templateId,
metaDataId: UIState.metaDataId,
docPath,
processedData: UIState.processedData,
processType,
totalPages,
dataOrigin,
};
response = await UserService.saveProcessedData(reqData);
// updating only data origin and processed data which may change
UIState.cvMetaDataOperations("update", _id, {
dataOrigin,
processedDataId: { _id: UIState.currentProcessedDataId, processedData: UIState.processedData },
});
UIState.setProcessedData(response.processedData);
// update state with latest data
UIState.updateCvData(response.metaDataId, response.updatedData, response.processedData);
}
if (currentScreen === "cvProcessing" || currentScreen === "cvProcess") {
const { userId, orgId, adminId, userType } = UserState.userData;
uType = userType;
reqData = {
orgId,
adminId,
userId,
templateId: UIState.selectedTemplateId,
metaDataId: UIState.metaDataId,
docPath: UIState.currentFolderPath ? UIState.currentFolderPath : UIState.currentFilePath,
processedData: UIState.processedData,
processType: UIState.processType ? UIState.processType : "-",
totalPages: UIState.totalPages,
dataOrigin,
};
response = await UserService.saveProcessedData(reqData);
}
if (response.success) {
UIState.updateCurrentMetaDataId(response.metaDataId);
NotificationManager.success("Data Saved", "Saved", 1500);
// setting is data saved flag true to prevent deletion of image from server
UIState.updateState("isDataSaved", true);
// after saving data fetch new data
// fetching new data only when processed new cv
if (uType) UIState.fetchMetaData(uType);
}
if (response.error) {
let errorMessage: string = response.error;
if (errorMessage.includes("metaData validation failed")) {
errorMessage = "nofication-name-email-required";
} else if (errorMessage.includes("duplicate key error collection")) {
errorMessage = "notifcation-processed-by-other-user";
} else if (errorMessage.includes("Email is already exists")) {
errorMessage = "notification-email-duplication";
} else {
errorMessage = "notification-error-occured";
}
NotificationManager.error(errorMessage, null, 5000);
}
// set graph status
UserState.setUserGraphApiCallStatus = "false";
};
const validate = (e) => {
e.preventDefault();
if (!dataOrigin) {
NotificationManager.error("data-origin-is-required", null, 5000);
} else {
onSaveData();
}
};
// * cleanup
// eslint-disable-next-line
useEffect(() => {
return () => {
setDataOrigin("");
};
}, []);
const action = "extractProcess";
const templateId = undefined;
// multipage
const onMultiImageProcess = async () => {
// const action = "tempProcess";
const markedData: any = { ...UIState.annotatePages };
const reqData = {
socketId: UIState.socketId,
userId: UserState.userData.userId,
orgId: UserState.userData.orgId,
multiPageInvoiceClasses: markedData,
processType: UIState.processType,
action,
totalPages: UIState.totalPages,
folderPath: UIState.currentFolderPath,
...UIState.templateConfigurations,
templateId,
};
await UserService.processImage(reqData);
};
// PROCESSING SINGLE PAGE
const onSingleImageProcess = async () => {
const markedData = UIState.annotatePages[UIState.currentSelectedPage];
// const action = "tempProcess";
const reqData = {
socketId: UIState.socketId,
userId: UserState.userData.userId,
orgId: UserState.userData.orgId,
imagePath: UIState.currentFilePath,
singlePageInvoiceClasses: [...markedData],
processType: UIState.processType,
action,
totalPages: UIState.totalPages,
...UIState.templateConfigurations,
templateId,
};
await UserService.processImage(reqData);
};
const onProcess = async () => {
try {
// set processing only for cv processing not on create edit
// if (isCreateEditScreen() === false)
UIState.setLoadingState("isProcessing", true);
if (UIState.totalPages === 1) {
await onSingleImageProcess();
}
if (UIState.totalPages > 1) {
await onMultiImageProcess();
}
UIState.increamentCVProcessingProgress(START);
LogService.info("CLASS COORDINATES 🔭 ", toJS(UIState.annotatePages));
// if (isCreateEditScreen()) {
// UIState.setLoadingState("isProcessing", false);
// history.push("/user/manageTemplate");
// }
} catch (error) {
LogService.error(error);
}
};
return (
<>
{/* Rocket animation code here */}
{!UIState.processedData && currentScreen === "cvProcess" && (
<Row>
{/* until auto processing is on going don't show extract data button */}
<div className="d-flex flex-column justify-content-center align-items-center m-auto mb-3">
{/* Extract Data */}
{UIState.isAutoProcessDone && !UIState.processedData && (
<Button color="success" className="font-weight-bold" onClick={onProcess}>
<IntlMessages id="extract-data" />
</Button>
)}
</div>
<div className="OutputViewSection__filler_div" />
<div className="d-flex justify-content-center align-items-center p-5">
<div
className={`w-50 ${UIState.isProcessing ? "vibrate-1" : ""} ${
UIState.isProcessedDataEmmited ? "slide-out-top" : ""
}`}
>
<img src={Rocket} alt="rocket" width="100%" style={{ transform: "rotate(-45deg)" }} />
</div>
<div>
<p className="h4 text-dark">{UIState.cvProcessingProgress}%</p>
</div>
</div>
</Row>
)}
{/* save button */}
<CardLayout className="mb-4" style={{ display: UIState.processedData ? "block" : "none" }}>
<CardBody>
<div className="d-flex flex-column justify-content-center align-items-center mt-3">
{!UIState.isDataSaved && currentScreen === "cvProcess" && (
<p className="h5">
<IntlMessages id="extraction-complated-save-data" />
</p>
)}
<SaveButton color="success" pill className="m-1" size="xl" outline onClick={onSaveData}>
<IntlMessages id="Save" />
</SaveButton>
</div>
<Row className="d-flex flex-column pl-4 pr-4 mb-3">
<p className="h6 font-weight-bold">
<IntlMessages id="cvstatus.data-origin" />
</p>
<textarea value={dataOrigin} onChange={onChangeDataOrigin} />
</Row>
{/* categorey and data origin fields show only when metadataid is set */}
{metaDataId && (
<div className="d-flex justify-content-end mb-3">
{/* <textarea className="textinput" value={category} onChange={onChangeCategory} onBlur={onCategoryFocusLoss} /> */}
{isSelectModalOpen && <CategorySelector isOpen={isSelectModalOpen} toggle={toggleSelectModal} />}
{/* <Select options={options} /> */}
<Button onClick={toggleSelectModal}>
<IntlMessages id="select-category" />
</Button>
</div>
)}
{UIState.processedData && <ReactTable data={UIState.processedData} />}
</CardBody>
</CardLayout>
</>
);
};
export default observer(OutPutViewSection);`
`
ReactTable.tsx
`/** #format */
/* eslint-disable react/jsx-props-no-spreading */
import { observer } from "mobx-react-lite";
import React from "react";
import { usePagination, useTable } from "react-table";
import { Pagination, PaginationItem, PaginationLink, Table } from "reactstrap";
// import Pagination from "../../containers/pages/Pagination";
import useStore from "../../mobx/UseStore";
import EditableCell from "./components/EditableCell";
import { ReactTableStyle } from "./Style";
// Set our editable cell renderer as the default Cell renderer
const defaultColumn = {
Cell: EditableCell,
};
// export default function Index({ columns, data, updateMyData, skipPageReset }) {
function Index({ data }) {
const { UIState } = useStore();
// table columns
const columns = React.useMemo(
() => [
{
Header: "Label",
accessor: "label",
},
{
Header: "Data",
accessor: "data",
},
// { Header: "Action", accessor: "action" },
],
[]
);
const [skipPageReset, setSkipPageReset] = React.useState(false);
const updateMyData = (rowIndex, columnId, value, isChecked) => {
// We also turn on the flag to not reset the page
setSkipPageReset(true);
UIState.updateProcessedData({ rowIndex, columnId, value, isChecked });
};
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
// Instead of using 'rows', we'll use page,
// which has only the rows for the active page
// The rest of these things are super handy, too ;)
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
defaultColumn,
autoResetPage: !skipPageReset,
initialState: { pageIndex: 0, pageSize: 5 },
pageCount: 2,
updateMyData,
},
usePagination
);
// useEffect(() => {
// setPageSize(2);
// }, []);
return (
<ReactTableStyle>
<div className="tableWrap">
<Table {...getTableProps()}>
{/* <thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column: any) => (
<th
{...column.getHeaderProps({
className: column.collapse ? "collapse" : "",
})}
>
{column.render("Header")}
</th>
))}
</tr>
))}
</thead> */}
<tbody {...getTableBodyProps()}>
{console.log()}
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell: any) => (
// <td
// {...cell.getCellProps({
// // className: cell.column.collapse ? "collapse" : "",
// className: "d-flex",
// })}
// >
<div>{cell.render("Cell")}</div>
// </td>
))}
{row.cells.map((cell) => {
console.log(cell);
return "";
})}
</tr>
);
})}
</tbody>
</Table>
</div>
<div className="Footer">
{/* <Pagination
currentPage={pageIndex - 1}
firstIsActive={!canPreviousPage}
lastIsActive={!canNextPage}
numberLimit={3}
totalPage={pageCount}
onChangePage={gotoPage}
/> */}
<Pagination aria-label="Page navigation " listClassName="justify-content-center" size="sm">
<PaginationItem>
<PaginationLink className="first" onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
<i className="simple-icon-control-start" />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink className="prev" onClick={() => previousPage()} disabled={!canPreviousPage}>
<i className="simple-icon-arrow-left" />
</PaginationLink>
</PaginationItem>
{/* {[...new Array(pageOptions.length).keys()].map((pageNumber) => (
<PaginationItem active={pageIndex === pageNumber}>
<PaginationLink onClick={() => gotoPage(pageNumber)}>{pageNumber + 1}</PaginationLink>
</PaginationItem>
))} */}
<PaginationItem active>
<PaginationLink onClick={() => gotoPage(pageIndex)}>{pageIndex + 1}</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink className="next" onClick={() => nextPage()} disabled={!canNextPage}>
<i className="simple-icon-arrow-right" />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink className="last" onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
<i className="simple-icon-control-end" />
</PaginationLink>
</PaginationItem>
</Pagination>
</div>
</ReactTableStyle>
);
}
export default observer(Index);
`
The child component that I am using as cell.
`/`** #format */
import { InputGroup, InputGroupAddon, InputGroupText, Input } from "reactstrap";
import React from "react";
import { observer } from "mobx-react-lite";
import styled from "styled-components";
import useStore from "../../../mobx/UseStore";
import IntlMessages from "../../../helpers/IntlMessages";
// Create an editable cell renderer
const EditableCell = ({
value: initialValue,
row: { index, original },
column: { id },
updateMyData, // This is a custom function that we supplied to our table instance
}) => {
const { UIState } = useStore();
// We need to keep and update the state of the cell normally
const [value, setValue] = React.useState(initialValue);
// const [check, setCheck] = React.useState(false);
const onChange = (e) => {
setValue(e.target.value);
};
// We'll only update the external data when the input is blurred
const onBlur = () => {
updateMyData(index, id, value, true);
// setCheck(true);
};
// If the initialValue is changed external, sync it up with our state
React.useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return (
<>
{/* adding editable text only for data not for label */}
{id === "label" && UIState.labelKeyMap[value] && (
<div className="font-weight-bold m-3">
{/* <IntlMessages id={`${value}`} /> */}
<IntlMessages id={`${UIState.labelKeyMap[value]}`} />
</div>
)}
{id === "data" && (
<InputGroup className="mb-3">
<InputGroupAddon addonType="prepend">
<InputGroupText>
<Input
addon
color="secondary"
type="checkbox"
aria-label="Checkbox for following text input"
checked={original.isChecked}
/>
</InputGroupText>
</InputGroupAddon>
<Input type="textarea" className="text-dark" width="100" value={value} onChange={onChange} onBlur={onBlur} />
</InputGroup>
)}
</>
);
};
export default observer(EditableCell);
`
The function that update the mobx state in another file.
`updateProcessedData({ rowIndex, columnId, value, isChecked }) {
console.log(`processed data -> ${toJS(JSON.stringify(this.processedData))}`);
this.processedData.forEach((row: any, index) => {
if (index === rowIndex) {
// updating the index of process data obj as with map not working
this.processedData[index] = {
...this.processedData[rowIndex],
[columnId]: value,
isChecked,
};
}
console.log(`processed after -> ${toJS(JSON.stringify(this.processedData))}`);
return row;
});
}`
I want to persist the data that I made changes in the page even i am navigating to other pages
I use two components for my checkout flow. I integrated Stripe card elements but whenever I type a card number, it refreshes at least 3 times before it stops and when I submit my data it doesn't seem to be well capture by my headless commerce. I followed this doc
When I submit this error shows:
v3:1 Uncaught (in promise) IntegrationError: We could not retrieve data from the specified Element.
Please make sure the Element you are attempting to use is still mounted.
at et (v3:1:94950)
at e._handleMessage (v3:1:103953)
at e._handleMessage (v3:1:74083)
at v3:1:101904
This is my PaymentOptions component - level 3 (inside UserPayment component):
//Required
import React, { useState, useContext } from "react";
import { loadStripe } from '#stripe/stripe-js';
import { Elements } from "#stripe/react-stripe-js";
import getConfig from 'next/config';
import { useRouter } from 'next/router';
import * as ga from '../../../lib/ga/index.js';
import { uid } from 'uid';
import CheckoutForm from './Checkout_Form_Custom';
//Styles
import { Flex, Box } from 'reflexbox';
import styled from '#emotion/styled';
import { LoaderInBlue, PaypalButton, CustomCardButton } from 'components/GlobalStyles/Buttons';
import { SimpleErrorMessage } from 'components/GlobalStyles/Errors'
//Context
import EcommerceContext from '../../../context/EcommerceContext';
const { publicRuntimeConfig } = getConfig();
const stripePromise = loadStripe(publicRuntimeConfig.STRIPE_PK_TEST);
function PaymentOptions({formData, token, cartItems}){
const router = useRouter();
const [processing, setProcessing] = useState('');
const [isStripe, setIsStripe] = useState(false);
const [isPaypal, setIsPaypal] = useState(false);
// Add auth context
const { error, buttonProcessing } = useContext(EcommerceContext)
const handleStripeButton = async (event) =>{
event.preventDefault();
setIsStripe(true);
const generateNewTransactionId = uid()
const totalPrice = cartItems && cartItems.length > 0 && cartItems.reduce((a, c) => a + c.unitPrice * c.quantity, 0);
}
const handlePaypalButton = async (event) =>{
event.preventDefault();
setIsStripe(false);
}
return(
<PaymentOptionsStyled className="payment-options">
{buttonProcessing &&
/** if something is loading, show the loading icon**/
<Flex justifyContent="center">
<LoaderInBlue />
</Flex>
}
{error && <SimpleErrorMessage />}
{
!isStripe && !isPaypal &&
/** if no payment is selected, show payment buttons **/
<>
<Flex justifyContent="center" className="card-option payment-option">
<CustomCardButton
handleStripeButton={handleStripeButton}
/>
</Flex>
<Flex justifyContent="center" className="paypal-option payment-option">
<PaypalButton
handlePaypalButton={handlePaypalButton}
formData={formData}
cartItems={cartItems}
/>
</Flex>
</>
}
{
isStripe &&
/**** if card payment is selected, show show Stripe Elements */
<Box className='center-box'>
<Flex justifyContent="center">
<Elements /*options={options}*/ stripe={stripePromise}>
<CheckoutForm token={token} />
</Elements>
</Flex>
<Flex my={20}>
<Box as="button" onClick={()=> setIsStripe(false)}>
{'Retour'}
</Box>
</Flex>
</Box>
}
</PaymentOptionsStyled>
)
}
const PaymentOptionsStyled = styled.div`
...
`
export default PaymentOptions
This is my CheckoutForm component - last level (inside PaymentOptions component):
//Required
import React, { useState, useEffect, useContext } from "react";
import getConfig from 'next/config';
import swell from 'swell-js';
import { useRouter } from 'next/router';
import {
useStripe,
useElements,
CardElement
} from "#stripe/react-stripe-js";
import { success } from 'components/Utils/pageUrls';
import {
submitOrder
} from '../../../context/CommerceServices';
import {
newIntent
} from 'components/Utils/apis';
//Styles
import { Flex, Box } from 'reflexbox';
import styled from '#emotion/styled';
import Checkmark from 'components/GlobalStyles/Animations/Checkmark';
import { LoaderInBlue, PayOrder, WaitingForPayment, SubmitPaymentButton } from 'components/GlobalStyles/Buttons';
//SEO & Cookies
import { setCookie, parseCookies, destroyCookie } from 'nookies';
//Contexts
import EcommerceContext from '../../../context/EcommerceContext';
const { API_URL } = process.env;
function CheckoutForm({formData, token, getPaymentIntentId}){
//Commerce context
const {
cartItems,
cart,
errorBeforePayment,
setErrorBeforePayment,
paymentIntent,
setPaymentIntent,
} = useContext(EcommerceContext);
//Defines states
const router = useRouter();
const [isData, setIsData] = useState(false);
const [succeeded, setSucceeded] = useState(false);
const [error, setError] = useState(null);
const [tokenized, setTokenized] = useState(null);
const [processing, setProcessing] = useState('');
const [disabled, setDisabled] = useState(true);
const [newOrderId, setNewOrderId] = useState('');
const [clientSecret, setClientSecret] = useState('');
const elements = useElements();
const stripe = useStripe();
//Fetch stripe paymentIntent
useEffect(async(ctx) => {
//Cart informations
const intentData = {
cartItems,
cart
}
//if user is logged in, fetch payment intent
if(token){
// Create paymentIntent as soon as the page loads
paymentIntent = await fetch(`${API_URL}${newIntent}`, {
method: 'POST',
body: JSON.stringify({intentData}),
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => {
//console.log('res payment:', res)
return res.json();
})
.then(data => {
console.log('data payment:', data)
if(data.info && data.info.clientSecret){
setClientSecret(data.info.clientSecret);
setPaymentIntent(data.info.newId);
setIsData(true)
}else{
setIsData(false)
}
if(data.info && data.info.newId){
setCookie(ctx, 'paymentIntentId', data.info.newId)
}
if(data.error){
setErrorBeforePayment(true)
}
});
}else{
return
}
}, []);
//Get form
const form = document.getElementById('payment-form');
//Tokenize card details with Swell
form && form.addEventListener('submit', function(event) {
event.preventDefault();
console.log('adding listener');
console.log('form', form)
//raise flag!
setProcessing(true);
console.log('before tokenize');
const tokenize = swell.payment.tokenize({
card: {
onError: (err) => {
//inform the customer there was an error
console.log('error tokenizing:', err);
},
onSuccess: () => {
//submit the form
console.log('succes tokenizing:', );
setTokenized(true);
if(tokenized){
console.log('tokenized so submit:', tokenized)
form.submit();
}
}
}
});
console.log('end listener');
setProcessing(false);
});
//Submit payment to Stripe and handle error
const handleSubmit = async ev => {
ev.preventDefault();
//raise flag!
setProcessing(true);
console.log('in submit func')
if(clientSecret && tokenized){
//if client secret is received, submit form
console.log('submiting payment to stripe')
const payload = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement)
}
});
console.log('end payload:')
if (payload && payload.error) {
setError(`Payment failed ${payload.error.message}`);
setProcessing(false);
} else {
console.log('succeed !:', payload)
setError(null);
setProcessing(false);
//const submitOrder = await swell.cart.submitOrder()
setSucceeded(true);
destroyCookie(null, 'paymentIntentId');
}
}else{
//if tokenization was unsuccessful
console.log('not tokenized so...bye!')
return
}
};
//handle payment success
/*if(succeeded) return (
<>
<CheckoutFormStyled>
<Flex flexDirection="column" justifyContent="center">
<p className=" checkout-success center">Paiement réussi!</p>
<Checkmark />
</Flex>
</CheckoutFormStyled>
{setTimeout(()=> router.push(success + '?orderId=' + `${newOrderId}&si=${successId}` ), 2000)}
</>
)*/
//Render Stripe Card Element
const customCardElement = swell && swell.payment.createElements({
card: {
elementId: '#card-element-id', // default: #card-element
options: {
style: {
base: {
//iconColor: '#c4f0ff',
fontWeight: 500,
fontFamily: 'Ideal Sans, system-ui, sans-serif',
fontSize: "16px",
},
},
},
onChange: event => {
// optional, called when the Element value changes
// Listen for changes in the CardElement
// and display any errors as the customer types their card details
//event.preventDefault();
setDisabled(event.empty);
setError(event.error ? event.error.message : "");
console.log('changing field !', event)
},
onSuccess: result => {
// optional, called on card payment success
console.log('result !');
},
onError: error => {
// optional, called on card payment error
console.log('error !');
setError(`Payment failed`);
setProcessing(false);
}
}
});
return(
<CheckoutFormStyled>
{
<form id="payment-form" onSubmit={(ev) => handleSubmit(ev)}>
{
!isData &&
/** if there is no clientSecret yet, show the loading icon **/
<Flex justifyContent="center"><LoaderInBlue /></Flex>
}
{
isData &&
/** When the clientSecret is ready, show Stripe CardElement and payment button**/
<>
<CardElement id="card-element-id"/>
<button as="button" className="ck-button order payment order-form-square"
disabled={processing || disabled || succeeded}
type="submit"
>
<Flex justifyContent='center' my={10}>
{ processing ?
<WaitingForPayment />
:
<PayOrder className="order-button-content"/>
}
</Flex>
</button>
</>
}
{/* Show any error that happens when processing the payment */}
{error &&
<Box as="div" className="card-error" role="alert">
{error}
</Box>
}
{/* Show a success message upon completion */}
<Box as ="p" className={succeeded ? "result-message" : "result-message hidden"}>
Payé avec succès ! see result in your
<Box as="a"
href={`https://dashboard.stripe.com/test/payments`}
>
{" "}
Stripe dashboard.
</Box> Refresh the page to pay again.
</Box>
</form>
}
{errorBeforePayment &&
<Box>Une erreur est survenue. Veuillez réessayer ou contacter Famous in Vogue.</Box>
}
</CheckoutFormStyled>
)
}
const CheckoutFormStyled = styled.div`
...
`
export default CheckoutForm
and lastly, this is my MultiStep component - level 1 (parent) UserPayment is level 2:
//Required
import { useContext } from 'react';
import { useForm, useStep } from 'react-hooks-helper';
import { UserIdentity, UserIdentityReview } from './Steps/User_Identity_Custom';
import { UserPaymentInactive, UserPayment } from './Steps/User_Payment_Custom';
import OrderSummary from 'components/Ecommerce/Order/Summary';
//Styles
import styled from '#emotion/styled';
import { Flex, Box } from 'reflexbox';
//contexts
import BreakPointContext from '../../../context/BreakPointContext';
const steps = [
{ id: 'détails de facturation'},
{ id: 'payment'},
];
function MultiStep ({cartItems, cart, token, auth_user, getPaymentIntentId, saveCartChanges}) {
const defaultData = {
firstnameInput: auth_user.myAddress && auth_user.myAddress.firstname,
lastnameInput: auth_user.myAddress && auth_user.myAddress.lastname,
emailInput: auth_user.myAddress && auth_user.email,
postcodeInput: '',
streetInput: '',
cityInput: '',
countryInput: ''
};
const { isBreakpoint } = useContext(BreakPointContext);
const [formData, setForm] = useForm(defaultData);
const {step, navigation} = useStep({
steps,
initialStep: 0,
});
const props = { formData, setForm, navigation };
if(isBreakpoint){
//if mobile
switch(step.id){
case 'détails de facturation':
return(
<CheckoutStyled >
<Flex className="checkout">
<Box clasName="order-summary checkout-step" width={isBreakpoint ? 1/1 : 1.5/3} mx={isBreakpoint ? 0 : 20}>
<OrderSummary {...props} cartItems={cartItems} />
</Box>
<Box clasName="checkout-steps" width={ isBreakpoint ? 1/1 : 2/3}>
<Box clasName="checkout-step" my={20}>
<UserIdentity {...props} auth_user={auth_user} cartItems={cartItems} cart={cart}/>
</Box>
<Box clasName="checkout-step" my={20}>
<UserPaymentInactive {...props} cartItems={cartItems} cart={cart}/>
</Box>
</Box>
</Flex>
</CheckoutStyled>
)
case 'payment':
return(
<CheckoutStyled>
<Flex className="checkout">
<Box clasName="order-summary" width={isBreakpoint ? 1/1 : 1.5/3} mx={isBreakpoint ? 0 : 20}>
<OrderSummary {...props} cartItems={cartItems} cart={cart} saveCartChanges={saveCartChanges}/>
</Box>
<Box clasName="checkout-steps" width={ isBreakpoint ? 1/1 : 2/3}>
<Box clasName="checkout-step" my={20}>
<UserIdentityReview {...props} auth_user={auth_user}/>
</Box>
<Box clasName="checkout-step" my={20}>
<UserPayment {...props} cartItems={cartItems} cart={cart} token={token} getPaymentIntentId={getPaymentIntentId} />
</Box>
</Box>
</Flex>
</CheckoutStyled>
)
}
}else{
//if desktop
switch(step.id){
case 'détails de facturation':
return(
<CheckoutStyled>
<Flex className="checkout">
<Box clasName="checkout-steps" width={ isBreakpoint ? 1/1 : 2/3}>
<Box clasName="checkout-step" my={20}>
<UserIdentity {...props} auth_user={auth_user} cartItems={cartItems} cart={cart}/>
</Box>
<Box clasName="checkout-step" my={20}>
<UserPaymentInactive {...props} cartItems={cartItems} cart={cart}/>
</Box>
</Box>
<Box clasName="order-summary checkout-step" width={isBreakpoint ? 1/1 : 1.5/3} mx={isBreakpoint ? 0 : 20}>
<OrderSummary {...props} cartItems={cartItems} cart={cart}/>
</Box>
</Flex>
</CheckoutStyled>
)
case 'payment':
return(
<CheckoutStyled>
<Flex className="checkout">
<Box clasName="checkout-steps" width={2/3}>
<Box clasName="checkout-step" my={20}>
<UserIdentityReview {...props} auth_user={auth_user}/>
</Box>
<Box clasName="checkout-step" my={20}>
<UserPayment {...props} cartItems={cartItems} cart={cart} token={token} getPaymentIntentId={getPaymentIntentId} />
</Box>
</Box>
<Box clasName="order-summary" width={1.5/3}>
<OrderSummary {...props} cartItems={cartItems} cart={cart} saveCartChanges={saveCartChanges} />
</Box>
</Flex>
</CheckoutStyled>
)
}
}
}
const CheckoutStyled = styled.div`
...
`
export default MultiStep
I removed all the useStates from CheckoutForm and the card payment form stopped refreshing when typing. I still have one error to fixed so my card details can be captured !
Thank you for your input.
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>
);
}
I hope you could help me with this part, so I have no problem with the stepper and no problem with the forms too when it comes to getting data input and validating fields with yup .
but I have a problem with the checkboxes and autocomplete. when I go back to a previous step, I lose the values I have already entered and validation no longer works with checkbox and autocomplete field.
There is an example of a hook that I made for the checkboxes that I will use later in a form :
const CheckBoxField = (props) => {
const { label, ...rest } = props;
const [field, meta, helper] = useField(props);
const { setValue } = helper;
const [touched, error] = at(meta, "touched", "error");
const isError = error && touched && true;
function __renderHelperText() {
if (isError) {
return <FormHelperText>{error}</FormHelperText>;
}
}
function _onChange(event) {
setValue(event.target.checked);
}
const configFormControl = {
...field,
...rest,
onChange: _onChange,
};
return (
<FormControl
component="fieldset"
{...configFormControl}
error={Boolean(isError)}
>
<FormControlLabel
// value={field.checked}
checked={field.checked}
label={label}
onChange={_onChange}
control={
<BpCheckbox
{...configFormControl}
// checked={field.checked}
color="primary"
/>
}
color="primary"
/>
{__renderHelperText()}
</FormControl>
);
};
And there is the code for validation :
import * as yup from "yup";
import signUpFormModel from "../signUpFormModel";
const {
formField: {
terms //Boolean , used in checkboxes, should be true
},
} = signUpFormModel;
const signUpValidationSchema = [
yup.object().shape({
[terms.name]: yup.boolean().oneOf([true], `${terms.requiredErrMsg}`),
//other fields ...
}),
//other forms ...
];
export default signUpValidationSchema;
My Initial value :
import signUpFormModel from "../signUpFormModel";
const {
formField: {
terms,
},
} = signUpFormModel;
const formInitialValue = {
[terms.name]: false,
};
export default formInitialValue;
and some props helper (used as model for my forms)
import React, { Fragment } from "react";
import { Link } from "#mui/material";
const signUpFormModel = {
formId: "registration",
formField: {
terms: {
type: "checkbox",
name: "terms",
id: "terms",
label: () => (
<Fragment>
J'accepte les{" "}
<Link href="#" variant="body2">
termes et conditions générales d'utilisation
</Link>{" "}
et la{" "}
<Link href="#" variant="body2">
politique de confidentialité
</Link>
.
</Fragment>
),
requiredErrMsg: "Vous devez accepter les termes et conditions",
},
},
};
export default signUpFormModel;
Finaly the form itself :
import React from "react";
import { Grid } from "#mui/material";
import CheckBoxField from "../CheckBoxField";
const PrimarySignUpForm = (props) => {
const {
formField: {
terms,
},
} = props;
return (
<Grid container spacing={2}>
<Grid item xs={12}>
<CheckBoxField name={terms.name} label={terms.label()} />
</Grid>
</Grid>
);
};
export default PrimarySignUpForm;
And this is how I made the stepper :
import React, { Fragment, useState } from "react";
import SignUpFormLogs from "../forms/SignUpFormLogs";
import { Formik, Form } from "formik";
import formInitialValuefrom from "../formModel/formInitialValue";
import signUpFormModel from "../formModel/signUpFormModel";
import signUpValidationSchema from "../formModel/signUpValidationSchema";
import {
Button,
Link,
Step,
StepLabel,
Stepper,
Typography,
} from "#mui/material";
import { Box } from "#mui/system";
const steps = ["step1"];
const { formId, formField } = signUpFormModel;
function _renderSteps(step) {
switch (step) {
case "step1":
return <SignUpFormLogs formField={formField} />;
default:
return <div>Not Found</div>;
}
}
function _sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const SignUp = () => {
const [activeStep, setActiveStep] = useState(0);
const currentValidationSchema = signUpValidationSchema[activeStep]; //activeStep
const isLastStep = activeStep === steps.length - 1;
async function _submitForm(values, actions) {
await _sleep(1000);
console.log(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
setActiveStep(activeStep + 1);
}
function _handleSubmit(values, actions) {
if (isLastStep) {
_submitForm(values, actions);
} else {
setActiveStep(activeStep + 1);
actions.setTouched({});
actions.setSubmitting(false);
}
}
function _handleBack() {
setActiveStep(activeStep - 1);
}
return (
<Fragment>
<Stepper activeStep={activeStep} sx={{ pt: 3, pb: 5 }}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
{activeStep === steps.length ? (
<Typography variant="h6">Last step</Typography>
) : (
<Formik
initialValues={formInitialValuefrom}
validationSchema={currentValidationSchema}
onSubmit={_handleSubmit}
>
{({ isSubmitting }) => (
<Form id={formId}>
{_renderSteps(steps[activeStep])}
{activeStep !== 0 && (
<Button
onClick={_handleBack}
disabled={isSubmitting}
variant="outlined"
color="primary"
sx={{ mt: 4, mb: 2 }}
>
Back
</Button>
)}
<Button
type="submit"
disabled={isSubmitting}
variant="contained"
color="primary"
sx={{ mt: 4, mb: 2 }}
>
{isLastStep ? "Enregistrer" : "Suivant"}
</Button>
{isSubmitting && (
<Typography variant="h6">submitting...</Typography>
)}
</Form>
)}
</Formik>
)}
</Fragment>
);
};
export default SignUp;
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.