So I've been struggling for a while with retrieving data from APIs and or retrieving local json files. I am using React axios but even with the normal fetch method I am having the same issues. When I fetch the endpoint and save the code, my jsx refreshes and the data appears on the screen but then when I refresh the page, it's no longer there and doesn't appear when I refresh again and again. I have no idea what I am doing wrong. I tried to retrieve the data on the parent and set it as props but still the same problem.
My child component:
import React, { useEffect, useState } from 'react';
import './Card.scss';
import axios from 'axios';
import { ellipsisIcon } from '../../constants/images';
import dataJson from './data.json';
const Card = ({ name, color, icon, currentTime }) => {
const [data, setData] = useState([]);
const [daily, setDaily] = useState([]);
const [weekly, setWeekly] = useState([]);
const [monthly, setMonthly] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios.get('data.json');
setData(result.data);
setData(
data.filter((item) => {
return item.title === name;
}),
);
setDaily(data[0].timeframes.daily);
setWeekly(data[0].timeframes.weekly);
setMonthly(data[0].timeframes.monthly);
};
fetchData();
}, []);
return (
<div className="card" style={{ backgroundColor: `${color}` }}>
<img src={icon} alt={`${name} icon`} />
<div className="card__container bg-blue">
<div className="card__top-container flex">
<p className="text-white ">{name}</p>
<div className="card__top__elipse-container">
<img src={ellipsisIcon} alt="ellipsis" />
</div>
</div>
<div className="card__bottom-container">
{currentTime === 0 && (
<>
<h1 className="fs-900 text-white">{daily.current}hrs</h1>
<div className="card__bottom__prev-container">
<p className="text-accent ">
Yesterday -<span>{daily.previous}hrs</span>
</p>
</div>
</>
)}
{currentTime === 1 && (
<>
<h1 className="fs-900 text-white">{weekly.current}hrs</h1>
<div className="card__bottom__prev-container">
<p className="text-accent ">
Last Week -<span>{weekly.previous}hrs</span>
</p>
</div>
</>
)}
{currentTime === 2 && (
<>
<h1 className="fs-900 text-white">{monthly.current}hrs</h1>
<div className="card__bottom__prev-container">
<p className="text-accent">
Last Month -<span>{monthly.previous}hrs</span>
</p>
</div>
</>
)}
</div>
</div>
</div>
);
};
export default Card;
My App (Parent):
import { useState, useEffect } from 'react';
import Card from './components/Card/Card';
import {
pbImage,
ellipsisIcon,
exerciseIcon,
playIcon,
careIcon,
socialIcon,
studyIcon,
workIcon,
} from './constants/images';
const cards = [
{
name: 'Exercise',
color: 'hsl(var(--clr-exercise))',
icon: exerciseIcon,
},
{
name: 'Play',
color: 'hsl(var(--clr-play))',
icon: playIcon,
},
{
name: 'Self Care',
color: 'hsl(var(--clr-care))',
icon: careIcon,
},
{
name: 'Social',
color: 'hsl(var(--clr-social))',
icon: socialIcon,
},
{
name: 'Study',
color: 'hsl(var(--clr-study))',
icon: studyIcon,
},
{
name: 'Work',
color: 'hsl(var(--clr-work))',
icon: workIcon,
},
];
function App() {
const [selectedTime, setSelectedTime] = useState(2);
return (
<div className="app bg-dark">
<div className="main__container grid">
<div className="side__card-container">
<div className="side__card__top flex">
<div className="side__card__top__pb-container">
<img
src={pbImage}
alt="pb"
className="side__card__top__pb-image pb-image"
/>
</div>
<div className="side__card__top__person-container">
<p className="fs-600 text-accent">Report for</p>
<h2 className="fs-800 text-white">Jeremy Robson</h2>
</div>
</div>
<div className="side__card__bottom">
<div>Daily</div>
<div>Weekly</div>
<div>Monthly</div>
</div>
</div>
{cards.map((card, _index) => (
<Card
key={_index}
name={card.name}
color={card.color}
icon={card.icon}
currentTime={selectedTime}
/>
))}
</div>
</div>
);
}
export default App;
As I mentioned in the comments, states are updated asynchronously. So, you should be careful when you use a state immediately after setting its value. In your case, you shouldn’t use the data state because you are not sure that it has a value. Try changing to this.
useEffect(() => {
const fetchData = async () => {
const result = await axios.get('data.json');
const filteredData = result.data.filter((item) => {
return item.title === name;
})
setData(filteredData);
// make sure result data isn’t empty
setDaily(result.data[0].timeframes.daily);
setWeekly(result.data[0].timeframes.weekly);
setMonthly(result.data[0].timeframes.monthly);
};
fetchData();
}, []); // "[]" makes the useEffect callback only run after the first render
Related
So I'm reading data from a smart contract and trying to map through it to display on a frontend but it keeps returning an error
data is undefined
I'll post my code below, I'm really looking forward to some help
import React , { useState , useEffect , Fragment } from 'react';
import {Link} from 'react-router-dom';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import 'react-tabs/style/react-tabs.css';
import CardModal from '../CardModal';
import MarketplaceJSON from "../../../Marketplace.json";
import axios from "axios";
const TodayPicks = () => {
const [dataTab] = useState(
[
{
id: 1,
title: "All",
},
]
)
const [visible , setVisible] = useState(8);
const showMoreItems = () => {
setVisible((prevValue) => prevValue + 4);
}
const [modalShow, setModalShow] = useState(false);
const [data, updateData] = useState();
const [dataFetched, updateFetched] = useState(false);
async function getAllNFTs() {
const ethers = require("ethers");
//After adding your Hardhat network to your metamask, this code will get providers and signers
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
//Pull the deployed contract instance
let contract = new ethers.Contract(MarketplaceJSON.address, MarketplaceJSON.abi, signer)
//create an NFT Token
let transaction = await contract.getAllNFTs()
//Fetch all the details of every NFT from the contract and display
const items = await Promise.all(transaction.map(async i => {
const tokenURI = await contract.tokenURI(i.tokenId);
let meta = await axios.get(tokenURI);
meta = meta.data;
let price = ethers.utils.formatUnits(i.price.toString(), 'ether');
let item = {
price,
tokenId: i.tokenId.toNumber(),
seller: i.seller,
owner: i.owner,
image: meta.image,
name: meta.name,
description: meta.description,
}
return item;
}))
updateFetched(true);
updateData(items);
}
if(!dataFetched)
getAllNFTs();
console.log(data);
return (
<Fragment>
<div className="tf-section sc-explore-2">
<div className="themesflat-container">
<div className="row">
<div className="col-md-12">
<div className="seclect-box style3">
<div id="artworks" className="dropdown">
<Link to="#" className="btn-selector nolink">All Artworks</Link>
<ul>
<li><span>Abstraction</span></li>
<li className="active"><span>Skecthify</span></li>
<li><span>Patternlicious</span></li>
<li><span>Virtuland</span></li>
<li><span>Papercut</span></li>
</ul>
</div>
<div id="sort-by" className="dropdown style-2">
<Link to="#" className="btn-selector nolink">Sort by</Link>
<ul>
<li><span>Top rate</span></li>
<li className="active"><span>Mid rate</span></li>
<li><span>Low rate</span></li>
</ul>
</div>
</div>
<div className="flat-tabs explore-tab">
<Tabs >
<TabList>
{
dataTab.map(data=> (
<Tab key={data.id} >{data.title}</Tab>
))
}
</TabList>
{
data.map(data =>(
<TabPanel key={data.tokenId}>
{
<div key={data.tokenId} className={`sc-card-product explode style2 mg-bt ${item.feature ? 'comingsoon' : '' } `}>
<div className="card-media">data
<Link to="/item-details-01"><img src={item.img} alt="Axies" /></Link>
<div className="button-place-bid">
<button onClick={() => setModalShow(true)} className="sc-button style-place-bid style bag fl-button pri-3"><span>Place Bid</span></button>
</div>
<Link to="/login" className="wishlist-button heart"><span className="number-like">{item.wishlist}</span></Link>
<div className="coming-soon">{data.feature}</div>
</div>
<div className="card-title">
<h5><Link to="/item-details-01">"{data.title}"</Link></h5>
</div>
<div className="meta-info">
<div className="author">
<div className="avatar">
<img src={data.imgAuthor} alt="Axies" />
</div>
<div className="info">
<span>Creator</span>
<h6> <Link to="/authors-02">{data.nameAuthor}</Link> </h6>
</div>
</div>
<div className="tags">{data.tags}</div>
</div>
<div className="card-bottom style-explode">
<div className="price">
<span>Current Bid</span>
<div className="price-details">
<h5>{data.price}</h5>
<span>= {data.priceChange}</span>
</div>
</div>
<Link to="/activity-01" className="view-history reload">View History</Link>
</div>
</div>
}
{
visible < data.length &&
<div className="col-md-12 wrap-inner load-more text-center">
<Link to="#" id="load-more" className="sc-button loadmore fl-button pri-3" onClick={showMoreItems}><span>Load More</span></Link>
</div>
}
</TabPanel>
))
}
</Tabs>
</div>
</div>
</div>
</div>
</div>
<CardModal
show={modalShow}
onHide={() => setModalShow(false)}
/>
</Fragment>
);
}
export default TodayPicks;
I was also thinking about creating a custom NFT card component to map through but does that work? Please help
You are using Array.map in data and you are not initialising it with [], so on initial load it shows data is undefined error . So Initialise data with [] in useState..
const [data, updateData] = useState([])
// while accessing the object values from data, I'm getting undefined in map
// ../data/section1
const data = [{
id: 1,
image: './images/homepage/xbox-games.png',
text: 'Buy Xbox games and consoles',
}, {
id: 2,
image: './images/homepage/shop_surface_devices.webp',
text: 'Shop surface devices',
}, {
id: 3,
image: './images/homepage/choose_your_ms_365.png',
text: 'Choose your Microsoft 365',
}, {
id: 4,
image: './images/homepage/shop_windows_10.png',
text: 'Shop Windows 10',
}]
export default data;
// the actual component
import data from "../data/section1";
const Section1 = () => {
return (
<>
<div class="mx-20">
{data.map((vals) => {
<div class="">
<img src={vals.image}/>
<p>{vals.text}</p>
</div>
})}
</div>
</>
)
}
export default Section1;
return JSX from the map
import data from "../data/section1";
const Section1 = () => {
return (
<>
<div class="mx-20">
{data.map((vals) => {
return (
<div class="">
<img src={vals.image}/>
<p>{vals.text}</p>
</div>
)
})}
</div>
</>
)
}
export default Section1;
I had the same problem, then tried the first bracket instead of the second, and it resolved the problem
import data from "../data/section1";
const Section1 = () => {
return (
<>
<div class="mx-20">
{data.map((vals) => (
<div class="">
<img src={vals.image}/>
<p>{vals.text}</p>
</div>
))}
</div>
</>
)
}
export default Section1;
If i switch between Transfer to Beneficiary and All Beneficiary tab the Select options get duplicated but if i change the second useEffect hook dependency to allBeneficiary.current instead of beneficiaries presently there, Select options doesnt duplicate, but the options are not rendered on the first render until I switch to All Beneficiary tab and back to Transfer Beneficiary
Below is the Transfer to Beneficiary code
// Hooks and Contexts
import React, { useState, useContext, useEffect, useRef } from "react";
import { TransferPointsContext } from "../../../../../context/TransferPoints";
import { LoaderContext } from "../../../../../context/Loading";
// Components
import TransferSummary from "../../../../common/modals/TransferSummary";
import Loading from "../../../../features/Loader/Loading";
// UI
import swal from "sweetalert";
import toastr from "toastr";
import Select from "react-select";
import "./css/transfer-points.css";
import { nanoid }from 'nanoid'
function TransferPoints() {
const [showTransferSummary, setShowTransferSummary] = useState(false);
const [transferSummaryData, setTransferSummaryData] = useState(false);
const [showBeneficiaryDataPage, setShowBeneficiaryDataPage] = useState(false);
const [checked, setChecked] = useState(false);
const [ options, setOptions ] = useState([])
const {
verifyCardNumber,
verifyCardState,
getBeneficiaryList,
beneficiaries, //list of beneficiaries from API
hideBeneficiaryDataPage,
setInputs,
inputs,
} = useContext(TransferPointsContext);
const { loading } = useContext(LoaderContext);
toastr.options.progressBar = true;
toastr.options = {
toastClass: "alert",
iconClasses: {
error: "alert-error",
info: "alert-info",
success: "alert-success",
warning: "alert-warning",
},
};
const allBeneficiaries = useRef([]);
useEffect(() => {
getBeneficiaryList();
}, [allBeneficiaries]);
useEffect(() => {
if (beneficiaries.data !== null) {
if (
beneficiaries.data.status === 0 &&
beneficiaries.data.success === false
) {
toastr.error("Failed to fetch user beneficiaries!", "error", {
iconClass: "toast-error",
});
console.log("beneficiaries", beneficiaries.data);
} else if (
beneficiaries.data.status === 1 &&
beneficiaries.data.success === true
) {
console.log('All beneficiary ', allBeneficiaries.current)
beneficiaries.data.data.forEach((beneficiary) => {
console.log('For each ', beneficiary)
allBeneficiaries.current.unshift({
value: beneficiary.membership_number,
label: `${beneficiary.first_name} ${beneficiary.last_name == null ? '' : beneficiary.last_name}`,
});
});
console.log('LENGTH ', allBeneficiaries.current.length)
}
}
}, [beneficiaries]);
useEffect(() => {
if (hideBeneficiaryDataPage) {
setShowBeneficiaryDataPage(false);
}
}, [hideBeneficiaryDataPage]);
const beneficiaryData = useRef({});
useEffect(() => {
if (verifyCardState.data !== null) {
if (
verifyCardState.data.status === 1 &&
verifyCardState.data.success === true
) {
setShowBeneficiaryDataPage(true);
beneficiaryData.current = {
name: `${verifyCardState.data.data.first_name} ${verifyCardState.data.data.last_name == null ? '' : verifyCardState.data.data.last_name}`,
};
toastr.success("Membership Id Validated!", "Success", {
iconClass: "toast-success",
});
return;
}
if (
verifyCardState.data.status === 0 &&
verifyCardState.data.success === false
) {
if (verifyCardState.data.message && !verifyCardState.data.data) {
toastr.error(verifyCardState.data.message, "Validation failed!", {
iconClass: "toast-error",
});
setShowBeneficiaryDataPage(false);
return;
}
setShowBeneficiaryDataPage(false);
const errorMessages = verifyCardState.data.data;
for (const error in errorMessages) {
toastr.error(errorMessages[error], "Validation Error!", {
iconClass: "toast-error",
});
}
return;
}
}
}, [verifyCardState]);
const handleSearchInput = (event) => {
const card_number = event.value;
setInputs((inputs) => ({
...inputs,
card_number,
}));
verifyCardNumber(card_number);
};
const proceedToTransfer = () => {
const amount = document.getElementById("amount").value;
if (amount.trim().length === 0) {
swal({
title: "Oops!",
text: `Amount field cannot be empty`,
icon: "error",
button: "Ok",
});
return;
}
setShowTransferSummary(!showTransferSummary);
setTransferSummaryData({
amount,
name: beneficiaryData.current.name,
membership_id: inputs.card_number,
save_beneficiary: (beneficiaryData.current.save_beneficiary == 1) ? 1 : 0,
});
};
const handleInputChange = (event) => {
event.persist();
setInputs((inputs) => ({
...inputs,
[event.target.name]: event.target.value,
}));
};
const handleChange = (event) => {
event.persist();
setChecked(event.target.checked);
const save_beneficiary = event.target.checked === true ? 1 : 0;
beneficiaryData.current.save_beneficiary = save_beneficiary;
};
console.log('LENGTH ', allBeneficiaries.current)
// console.log('current beneficiaries ', (allBeneficiaries.current) )
// console.log('CHECKED ', beneficiaryData.current.save_beneficiary )
// let id = nanoid()
return (
<div>
{/* {loading ? <Loading /> : ""} */}
<form action="#">
<div className="row">
<div className="col-md-12">
<div className="form-group">
<label htmlFor="acc-email">Select Beneficiary </label>
<Select
onChange={handleSearchInput}
className="basic-single"
classNamePrefix="select"
isClearable="true"
isSearchable="true"
name="beneficiary_card_number"
defaultValue="Select"
options={allBeneficiaries.current} //THIS RETURNS DUPLICATED VALUE ON NAVIGATING TO ALL BENEFICIARIES AND BACK
/>
</div>
<h6 class="mt-3 heading-border border-0">OR</h6>
<div className="row align-items-center justify-content-between">
<div className="col-md-8">
<label htmlFor="card_number">Enter Membership Id</label>
<input
type="text"
className="form-control"
name="card_number"
onChange={handleInputChange}
value={inputs.card_number}
/>
</div>
<div className=" col-4 " style={{marginTop: '30px', paddingLeft: '10px', textAlign: 'end',}}>
<button
onClick={() => verifyCardNumber(inputs.card_number)}
type="button"
className="btn-lg btn btn-primary"
>
Validate Id
</button>
</div>
</div>
</div>
{showBeneficiaryDataPage === true ? (
<div className="col-sm-12">
<h6 class="mt-3 heading-border border-0"></h6>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label htmlFor="acc-name">Name</label>
<input
type="text"
className="form-control"
id="acc-name"
required
disabled
name="acc-name"
value={beneficiaryData.current.name}
/>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label htmlFor="acc-lastname">Membership Id</label>
<input
type="text"
className="form-control"
id="acc-lastname"
required
disabled
name="acc-lastname"
value={inputs.card_number}
/>
</div>
</div>
</div>
<div className="row">
<div className="col-md-12">
<div className="form-group">
<label htmlFor="acc-lastname">Amount</label>
<input
type="text"
className="form-control"
id="amount"
required
name="amount"
onChange={handleInputChange}
value={inputs.amount}
/>
</div>
</div>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
checked={checked}
name="save_beneficiary"
onChange={handleChange}
/>
<span className="ml-3 font-weight-bold terms-condition">
Save Beneficiary
</span>
</div>
<div className="mb-2"></div>
<div className="form-footer">
<div className="col-md-12 d-flex justify-content-center">
<button
onClick={() => proceedToTransfer()}
type="button"
className="btn-lg w-50 btn btn-primary"
>
Proceed
</button>
</div>
</div>
</div>
) : (
""
)}
</div>
</form>
{showTransferSummary === true ? (
<TransferSummary data={transferSummaryData} setShowTransferSummary={setShowTransferSummary} setShowBeneficiaryDataPage={setShowBeneficiaryDataPage}/>
) : (
""
)}
</div>
);
}
export default React.memo(TransferPoints);
This is the code for All Beneficiaries
import React, { useState, useContext, useEffect, useRef } from "react";
import "./css/transfer-points.css";
import { TransferPointsContext } from "../../../../../context/TransferPoints";
import Loading from "../../../../features/Loader/Loading";
import swal from "sweetalert";
import { LoaderContext } from "../../../../../context/Loading";
import toastr from "toastr";
import axios from 'axios'
import { ToastContainer, toast } from 'react-toastify';
function ShowAllBeneficiariesPage() {
const [ data, setData ] = useState([])
const {
getBeneficiaryList,
beneficiaries,
removeBeneficiary,
state,
} = useContext(TransferPointsContext);
const { loading } = useContext(LoaderContext);
toastr.options.progressBar = true;
toastr.options = {
toastClass: "alert",
iconClasses: {
error: "alert-error",
info: "alert-info",
success: "alert-success",
warning: "alert-warning",
},
};
const handleDelete = (id) => {
swal({
title: "Are you sure you want to remove beneficiary?",
text: "You won't be able to revert this!",
icon: "warning",
buttons: ["Cancel", "Proceed!"],
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
}).then((result) => {
if (result === true) {
removeBeneficiary(id);
console.log('ID OF RECEIVER ', id)
const newBeneficiary = data.filter(add => add.id !== id)
setData(newBeneficiary)
toastr.success("Beneficiary Removed !", "Success", {
iconClass: "toast-success",
});
}
});
};
const fetchData = () => {
axios.get(`user/beneficiaries`)
.then( res => setData(res.data.data ))
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
{/* {loading ? <Loading /> : ""} */}
{ data.length === 0 ?
(
<div style={{textAlign: 'center'}}>No beneficiaries found</div>
)
:
(
<div className="col-sm-12">
{data.map((item) => {
console.log('Beneficiary Data ', data)
return (
<p className="mb-1 p-4 beneficiary-list">
{item.first_name} {item.last_name} - {item.membership_number}
<i
style={{ cursor: "pointer" }}
onClick={() => handleDelete(item.id)}
class="float-right fas fa-trash"
></i>{" "}
</p>
);
})}
</div>
)}
</div>
);
}
export default (ShowAllBeneficiariesPage);
I can't completely fix your issue, because I'd need more context and time, but I've found some issues on your code.
Never ever ever, have something in your render code that has a reference to a useRef variable. When a useRef value changes, react will completely ignore it and will not update your component. Use setState for those.
It sounds like your allBeneficiaries instead of being a ref or a state it's just derived state: It looks like it's a derived value from beneficiaries. In this case, you don't need to use any hook, just declare it as a const (e.g. const allBeneficiaries = getBeneficiaries(beneficiaries)). If you have performance issues, then consider using useMemo, but it should not be needed.
Never use a useRef as a dependency value in a useEffect - Same thing, react doesn't care about ref values, so you'll have unexpected behaviour there (effects retriggering when it shouldn't, effects not triggering when they should)
Try to avoid useEffect as much as posible. It should only be used for specific cases, such as fetching something from a server or manipulating the dom. For the rest of them, it's just problematic, best avoided.
Using allBeneficiaries (or any other ref object) as a dependency for a hook won't help you at all. The ref object's identity will never change over the lifetime of a component.
If you want to run an effect/... when the value boxed within the allBeneficiaries ref changes, the dependency will need to be allBeneficiaries.current.
Beside that, there's no good reason to use a ref for allBeneficiaries. Since it affects rendering, you will want to save it as a state atom (useState).
I have Rails Application with React On Rails, recently upgraded webpack 3 to webpack 4
Rails : 4
Ruby: 2+
React: 16.9
Webpack: 4.41.5
everything works well except below code which works well with webpack 3
In the console I see below error
Uncaught Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
An error occurred during a React Lifecycle function
import Error from '#material-ui/icons/Error';
import MoreHoriz from '#material-ui/icons/MoreHoriz';
import { Button, FixedFooterModal, SafeAnchor, Util } from 'lib-components';
import moment from 'moment-timezone';
import prettyBytes from 'pretty-bytes';
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import Helpers from '../../../lib/lib-components/src/js/services/Helpers'; // eslint-disable-line
import styles from './ExportForm.css';
interface Export {
created_at: string;
filename: string;
filesize: number;
id: number;
project_id: number;
status: string;
updated_at: string;
options: string;
}
function parseOptions(options: string) {
return options
.split(',')
.map(option => {
switch (option) {
case 'include_done_stories':
return 'Done';
case 'include_current_backlog_stories':
return 'Current/Backlog';
case 'include_icebox_stories':
return 'Icebox';
case 'include_epics':
return 'Epics';
case 'include_attachments':
return 'Attachments';
case 'include_project_history':
return 'Project History';
}
})
.join(', ');
}
const ExportStatus: React.FunctionComponent<{
status: string;
projectId: number;
exportId: number;
csrfToken: string;
setModalOpen: (a: boolean) => void;
}> = ({ status, projectId, exportId, csrfToken, setModalOpen }) => {
const handleRetryClick = useCallback(() => {
Helpers.update(csrfToken, {
method: 'GET',
url: `/projects/${projectId}/export/${exportId}/retry`,
});
setModalOpen(true);
}, [projectId, exportId, csrfToken, setModalOpen]);
switch (status) {
case 'failed':
return (
<div data-aid='ExportForm__failed' className={styles['ExportForm__failed']}>
<div className={styles['ExportForm__failedIcon']}>
<Error fontSize='small' style={{ marginRight: '4px', color: '#CB2B1E' }} />
<span>Export failed</span>
</div>
<div data-aid='ExportForm__retryLink' className={styles['ExportForm__retryLink']}>
<SafeAnchor onClick={handleRetryClick}>retry</SafeAnchor>
</div>
</div>
);
case 'completed':
return (
<div data-aid='ExportForm__completed' className={styles['ExportForm__completed']}>
<div className={styles['ExportForm__completedIcon']}>
<CheckCircle fontSize='small' style={{ marginRight: '4px', color: '#6D902A' }} />
<span>Complete</span>
</div>
<div className={styles['ExportForm__completedDownloadLink']}>
<SafeAnchor href={`/projects/${projectId}/export/${exportId}/download`}>download</SafeAnchor>
</div>
</div>
);
default:
case 'in progress':
return (
<div className={styles['ExportForm__inProgressIcon']}>
<div className={styles['ExportForm__inProgressIconBg']}>
<MoreHoriz fontSize='small' />
</div>
<span data-aid='ExportForm__inProgress' className={styles['ExportForm__inProgress']}>
In Progress
</span>
</div>
);
}
};
const ExportForm: React.FunctionComponent<{
csrfToken: string;
id: number;
exports: Export[];
timezone: string;
}> = ({ csrfToken, id, exports, timezone }) => {
const [modalOpen, setModalOpen] = useState(false);
const [includeDoneStories, setIncludeDoneStories] = useState(true);
const [includeCurrentBacklogStories, setIncludeCurrentBacklogStories] = useState(true);
const [includeIceboxStories, setIncludeIceboxStories] = useState(true);
const [includeEpics, setIncludeEpics] = useState(true);
const [includeAttachments, setIncludeAttachments] = useState(true);
const [includeProjectHistory, setIncludeProjectHistory] = useState(true);
const setDoneStoriesFromEvent = useCallback(e => setIncludeDoneStories(e.target.checked), []);
const setCurrentBacklogStoriesFromEvent = useCallback(e => setIncludeCurrentBacklogStories(e.target.checked), []);
const setIceboxStoriesFromEvent = useCallback(e => setIncludeIceboxStories(e.target.checked), []);
const setEpicsFromEvent = useCallback(e => setIncludeEpics(e.target.checked), []);
const setAttachmentsFromEvent = useCallback(e => setIncludeAttachments(e.target.checked), []);
const setProjectHistoryFromEvent = useCallback(e => setIncludeProjectHistory(e.target.checked), []);
const handleExportClicked = useCallback(() => {
Helpers.update(
csrfToken,
{
method: 'POST',
url: `/projects/${id}/export`,
},
{
options: {
include_done_stories: includeDoneStories,
include_current_backlog_stories: includeCurrentBacklogStories,
include_icebox_stories: includeIceboxStories,
include_epics: includeEpics,
include_attachments: includeAttachments,
include_project_history: includeProjectHistory,
},
}
);
setModalOpen(true);
}, [
csrfToken,
id,
includeDoneStories,
includeCurrentBacklogStories,
includeIceboxStories,
includeEpics,
includeAttachments,
includeProjectHistory,
]);
const handleCloseModal = useCallback(() => {
setModalOpen(false);
Util.windowLocation().assign(`/projects/${id}/export`);
}, [id]);
const justRefresh = useMemo(() => exports.some(e => e.status === 'in progress'), [exports]);
useEffect(() => {
let timer: ReturnType<typeof setTimeout>;
if (justRefresh) {
timer = setTimeout(() => Util.windowLocation().reload(), 30000);
}
return () => {
clearTimeout(timer);
};
}, [justRefresh]);
return (
<div className={styles['ExportForm']}>
<h2>Create New Export</h2>
<p>
Stories, Epics, and Project History will be exported as a CSV. All files will be available to download from the
exports section below. Files are available for two weeks.
</p>
<div className={styles['ExportForm__options']}>
<div className={styles['ExportForm__option']}>
<label>
<input type='checkbox' checked={includeDoneStories} onChange={setDoneStoriesFromEvent} />
All Done Stories
</label>
</div>
<div className={styles['ExportForm__option']}>
<label>
<input
type='checkbox'
checked={includeCurrentBacklogStories}
onChange={setCurrentBacklogStoriesFromEvent}
/>
All Current/Backlog Stories
</label>
</div>
<div className={styles['ExportForm__option']}>
<label>
<input type='checkbox' checked={includeIceboxStories} onChange={setIceboxStoriesFromEvent} />
All Icebox Stories
</label>
</div>
<div className={styles['ExportForm__option']}>
<label>
<input type='checkbox' checked={includeEpics} onChange={setEpicsFromEvent} />
All Epics
</label>
</div>
<div className={styles['ExportForm__option']}>
<label>
<input
data-aid='ExportForm__attachments'
type='checkbox'
checked={includeAttachments}
onChange={setAttachmentsFromEvent}
/>
All Attachments
</label>
</div>
<div className={styles['ExportForm__option']}>
<label>
<input
data-aid='ExportForm__attachments'
type='checkbox'
checked={includeProjectHistory}
onChange={setProjectHistoryFromEvent}
/>
Project History
</label>
</div>
</div>
<Button
label={<span>Export</span>}
size='large'
type='primary'
data-aid='ExportForm__exportButton'
onClick={handleExportClicked}
/>
{modalOpen && (
<FixedFooterModal
buttons={[
{
props: {
label: 'Close',
type: 'lined',
onClick: handleCloseModal,
align: 'right',
'data-aid': 'ExportForm__closeModal',
},
},
]}
title='Export in progress'
onClose={handleCloseModal}
noScroll={true}
>
<div className={styles['ExportForm__inProgressModal']}>
<p>We're on it! We will send you an email once your data is available to download.</p>
<p>
Check back here to find all of your available exports. You can access your exported data here for two
weeks.
</p>
</div>
</FixedFooterModal>
)}
<h2>Exports</h2>
{exports.length > 0 ? (
exports.map(exp => (
<div key={exp.id} className={styles['ExportForm__export']} data-aid='ExportForm__export'>
<div className={styles['ExportForm__exportInfo']}>
<div className={styles['ExportForm__exportTimes']}>
<span data-aid='ExportForm__created'>
{moment.tz(exp.created_at, timezone).format('MMM Do, YYYY [at] h:mm A z')}
</span>
{exp.filesize ? ` (${prettyBytes(exp.filesize)}) • ` : ` • `}
<span data-aid='ExportForm__expires'>
Expires on {moment.tz(exp.created_at, timezone).add(14, 'days').format('MMM Do, YYYY')}
</span>
</div>
<div className={styles['ExportForm__exportOptions']}>
<span data-aid='ExportForm__exportOptions'>({parseOptions(exp.options)})</span>
</div>
</div>
<div className={styles['ExportForm__download']}>
<ExportStatus
status={exp.status}
projectId={exp.project_id}
exportId={exp.id}
csrfToken={csrfToken}
setModalOpen={setModalOpen}
/>
</div>
</div>
))
) : (
<div className={styles['ExportForm__emptyMessage']} data-aid='ExportForm__emptyMessage'>
<p>No exports have been created.</p>
</div>
)}
</div>
);
};
export default ExportForm;
I tried following solutions for similar error but of no help
Checked for multiple versions of react
disabling react hot loader and setConfig pureSFC to true
Used React.FunctionComponent
Also this componenet is rendered by React.lazy() later I figured all the components rendered by lazy loading have inavlid hook call
I am still new in React and Redux. So, I know the existence of redux-form, but Im not intend to use in this project. So, what I am doing is creating a form without using redux-form. This form will grab the data from the reducers and pass it to backend API.
This is my main CreateListing.jsx page.
// #flow
import React from 'react';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import { SEOService } from '[services]';
import CreateListingFormPage1 from './CreateListing/CreateListingFormPage1';
import CreateListingFormPage2 from './CreateListing/CreateListingFormPage2';
import CreateListingFormPage3 from './CreateListing/CreateListingFormPage3';
import WhereAmI from './CreateListing/WhereAmI';
import SuccessCreateListing from './CreateListing/SuccessCreateListing';
type Props = {
...props...
};
class CreateListing extends React.Component<Props> {
getPageBySequence(pagenum) {
// depending on whether User is logged in or not, show/hide the Login/Signup form which is Page3
let sequence = [ CreateListingFormPage1, CreateListingFormPage2, CreateListingFormPage3 ];
if (this.props.isLoggedIn) {
sequence = [ CreateListingFormPage1, CreateListingFormPage2, CreateListingFormPage2 ];
}
return sequence[pagenum-1];
}
getSubmitCreateListing = (e) => {
e.preventDefault();
const propertyType = this.props.listingType;
const propertyName = this.props.suggestedBuildings.selected;
const propertyBuildingType = this.props.propertyBuildingType;
const bedrooms = this.props.bed;
const bathrooms = this.props.bath;
const price = this.props.price;
const builtUp = this.props.builtUp;
const title = this.props.title;
const tenure = this.props.tenure;
const description = this.props.description;
/* IN CASE USER NOT YET LOGGGED IN */
if(this.props.isLoggedIn === false) {
const email = this.props.email;
const password = this.props.password;
this.props.cacheCreateListing({ email, password, propertyType, propertyName, propertyBuildingType, bedrooms, bathrooms, price, builtUp, title, tenure, description });
}
this.props.cacheCreateListing({ propertyType, propertyName, propertyBuildingType, bedrooms, bathrooms, price, builtUp, title, tenure, description });
if(CreateListingFormPage1.landedTypes.includes(propertyBuildingType)) {
this.props.geocodingRequired(true);
}
else {
this.props.geocodingRequired(false);
}
this.props.onSubmitCreateListing();
}
onAuthenticateAndCreateListingButton() {
if(this.props.isLoggedIn) {
return(
<div role="presentation">
<div className={`column ${this.props.formCurrentPage === 1 ? '':'displayNone'}`}>
<button type="button" className="Button button-next is-red" onClick={this.props.onNextClick}>
NEXT
</button>
</div>
<div className={`column ${this.props.formCurrentPage === 2 || this.props.formCurrentPage === 3 ? '':'displayNone'}`}>
<button type="submit" className="Button button-create is-red" onClick={this.props.onLoadingCreateListing}>
CREATE LISTING
</button>
</div>
</div>
)
}
return <div className={`column ${this.props.formCurrentPage < 3 ? '':'displayNone'}`}>
<button type="button" className="Button button-next is-red" onClick={this.props.onNextClick}>
NEXT
</button>
</div>
}
render() {
if(this.props.isListingCreated){
return <SuccessCreateListing />;
}
else if(this.props.isListingLoading){
return <div className="create-listing-spinner" />
}
const CurrentPage = this.getPageBySequence(this.props.formCurrentPage);
return (
<div className={`CreateListing${this.props.isMobile ? '' : ' is-desktop'}`}>
<Helmet>
<title>{ SEOService.getMetaTitle('Create Property Listing') }</title>
{ SEOService.getCanonicalTag('/blogs') }
</Helmet>
<section className="CreateListing--Main">
<div className="CreateListing--Container">
<div className="CreateListing--WhereAmI">
<WhereAmI page={this.props.formCurrentPage} />
</div>
<div className="CreateListing--Body">
<form className="CreateListing--Form" onSubmit={ this.getSubmitCreateListing }>
<CurrentPage />
<div className='columns'>
<div className='column'/>
{/* CHANGE THIS this.props.formCurrentPage < 3 later */}
<div className={`column ${this.props.formCurrentPage > 1 && this.props.formCurrentPage < 4 ? '':'displayNone'}`}>
<button type="button" className="Button button-back" onClick={this.props.onPrevClick}>
BACK
</button>
</div>
{ this.onAuthenticateAndCreateListingButton() }
<div className='column'/>
</div>
</form>
</div>
</div>
</section>
</div>
);
}
};
const MapStateToProps = (state: State) => ({...});
const MapDispatchToProps = (dispatch: Dispatch) => ({
onLoadingCreateListing: () => dispatch({type: 'CREATE_LISTING_LOADING'}),
onSubmitCreateListing: () => dispatch({type: 'CREATE_LISTING_SUBMIT_FORM'}),})
export default connect(MapStateToProps,MapDispatchToProps)(CreateListing);
So, my <input type="text" /> are all from CreateListingFormPage1, CreateListingFormPage2 and CreateListingFormPage3 and put together in <CurrentPage />. My <form onSubmit={...}></form> is in this CreateListing.jsx page. Im not whether it is allowed to do it like this.
So, when I click submit, I got warning of Form submission canceled because the form is not connected.
My example of <input type="" /> in CreateListingFormPage1 are:
// #flow
import React from 'react';
import { connect } from 'react-redux';
import {Dropdown} from '[elements]';
type Props = {...props...};
class CreateListingFormPage2 extends React.Component<Props> {
static get selectTenure() { return ["Select Tenure"].concat(this.tenureTypes) };
static get selectTitle() { return ["Select Title"].concat(this.titleTypes) };
static get selectBedroom() { return["Select Bedrooms no"].concat(this.bedroomNo) };
static get selectBathroom() { return["Select Bathrooms no"].concat(this.bathroomNo) };
static get tenureTypes(){
return[
"FREEHOLD",
"LEASEHOLD",
"OTHERS"
]};
static get titleTypes(){
return[
"RESIDENTIAL",
"COMMERCIAL",
"INDUSTRIAL"
]};
static get bedroomNo(){
return[
"1",
"2",
"3",
"4",
"5"
]};
static get bathroomNo(){
return[
"1",
"2",
"3",
"4",
"5"
]};
get selectTenure() { return this.constructor.selectTenure; }
get selectTitle() { return this.constructor.selectTitle; }
get selectBedroom() { return this.constructor.selectBedroom; }
get selectBathroom() { return this.constructor.selectBathroom; }
get tenureTypes() { return this.constructor.tenureTypes; }
get titleTypes() { return this.constructor.titleTypes; }
get bedroomNo() { return this.constructor.bedroomNo; }
get bathroomNo() { return this.constructor.bathroomNo; }
hasInputError = (name) => {
if (this.props.errors[name]) {
return ' is-error';
}
return '';
}
render() {
return (
<div className={`Listing--Create${ this.props.isMobile ? '' : ' is-desktop' }`} id='form-second-page'>
{/* <form className="Listing--form"> */}
<div className="Listing--bedrooms-bathrooms">
<div className="type-title">No. of Bedrooms</div>
<Dropdown namespace="bedroom" selected={ this.selectBedroom[0] } options={ this.selectBedroom } onOptionSelect={ this.onBedroomDropdownSelect }/>
<div className="type-title">Asking Price</div>
<input className={`text-input price-input${ this.hasInputError('price')}`} type="text" onChange={ (e) => this.props.onPrice(e.currentTarget.value) } value={this.props.price} placeholder="RM"/>
</div>
<div className="Listing--price-built-up">
<div className="type-title">No. of Bathrooms</div>
<Dropdown namespace="bathroom" selected={ this.selectBathroom[0] } options={ this.selectBathroom } onOptionSelect={ this.onBathroomDropdownSelect }/>
<div className="type-title">Built-up Size</div>
<input className={`text-input built-up-input${ this.hasInputError('built_up_size')}`} type="text" onChange={ (e) => this.props.onBuiltUpSize(e.currentTarget.value) } value={this.props.builtUp} placeholder="sqft."/>
</div>
<div className="Listing--tenure">
<div className="type-tenure">Select Tenure</div>
<Dropdown namespace="tenure" selected={ this.selectTenure[0] } options={ this.selectTenure } onOptionSelect={ this.onTenureDropdownSelect }/>
</div>
<div className="Listing--title">
<div className="type-title">Select Title</div>
<Dropdown namespace="title" selected={ this.selectTitle[0] } options={ this.selectTitle } onOptionSelect={ this.onTitleDropdownSelect }/>
</div>
<div className="Listing--photos">
<div className="type-title">Upload Photos</div>
<button className={`text-input photos-input${ this.hasInputError('photos')}`}>Click to upload</button>
</div>
<div className="Listing--description">
<div className="type-title">Describe your property</div>
<textarea className={`text-input description-input${ this.hasInputError('description')}`} onChange={ (e) => this.props.onDescription(e.currentTarget.value) } value={this.props.description} placeholder="Describe your property"/>
</div>
</div>
);
}
};
const MapStateToProps = (state: State) => ({
...
});
const MapDispatchToProps = (dispatch: Dispatch) => ({
...
})
export default connect(MapStateToProps, MapDispatchToProps)(CreateListingFormPage2);
Basically, there is nothing wrong with my redux store. All the value of the input is captured successfully. The problem is when submitting the form, either the onSubmit or my form structure method is incorrect.
This is CreateListing.js reducer should it be helpful:
const INITIAL_STATE= {
isListingLoading: false,
isListingCreated: false,
}
const CreateListing = (state = INITIAL_STATE, action) => {
switch(action.type){
case 'CREATE_LISTING_LOADING':
return Object.assign({}, state, {isListingLoading: true});
case 'CREATE_LISTING_SUBMIT_FORM':
return Object.assign({}, state, {isListingCreated: true});
default:
return state;
} }
export default CreateListing;
Any help is much appreciated.
if you have any other buttons in your form you should add type="button".
so make changes like this.
<button type="button" className="Button button-create is-red" onClick={this.props.onLoadingCreateListing}>
CREATE LISTING
</button>