Using React, and antd
I have the following code in my component:
<Upload
action={HttpService.getBaseUrl(`post_import_csv`, HttpService.AuditcoreAPIBasePath)}
headers={{"Authorization": `Bearer ${AuthHelper.getAuthKey()}`}}
showUploadList={false}
multiple={false}
beforeUpload={(file: RcFile): PromiseLike<any> => {
this.setCSV(file);
return new Promise((resolve) => {
this.state.requestUpload.pipe(take(1)).subscribe(() => {
resolve(file);
console.log('resolved')
});
})
}}></Upload>
Basically I want my beforeUpload to wait for the user to click on a button before uploading the file. I did so by returning a Promise and waiting for a rxjs Suject that is triggered on button click to resolve the promise. Pretty much following the doc
Here is the button code :
<Button
onClick={(e): void => {
this.state.requestUpload.next(true);
}}
>
Upload
</Button>
It works nice, but the file is never uploaded, I do see my log resolved but there is no trace of network call in my console.
I fixed using this approach which is cleaner :
https://codesandbox.io/s/xvkj90rwkz
Basically, having a custom function that handle upload. It doesn't explain why my tricky solution was not working, but I got it working.
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Upload, Button, Icon, message } from 'antd';
import reqwest from 'reqwest';
class Demo extends React.Component {
state = {
fileList: [],
uploading: false,
};
handleUpload = () => {
const { fileList } = this.state;
const formData = new FormData();
fileList.forEach(file => {
formData.append('files[]', file);
});
this.setState({
uploading: true,
});
// You can use any AJAX library you like
reqwest({
url: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
method: 'post',
processData: false,
data: formData,
success: () => {
this.setState({
fileList: [],
uploading: false,
});
message.success('upload successfully.');
},
error: () => {
this.setState({
uploading: false,
});
message.error('upload failed.');
},
});
};
render() {
const { uploading, fileList } = this.state;
const props = {
onRemove: file => {
this.setState(state => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
return {
fileList: newFileList,
};
});
},
beforeUpload: file => {
this.setState(state => ({
fileList: [...state.fileList, file],
}));
return false;
},
fileList,
};
return (
<div>
<Upload {...props}>
<Button>
<Icon type="upload" /> Select File
</Button>
</Upload>
<Button
type="primary"
onClick={this.handleUpload}
disabled={fileList.length === 0}
loading={uploading}
style={{ marginTop: 16 }}
>
{uploading ? 'Uploading' : 'Start Upload'}
</Button>
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById('container'));
Related
I'm trying to test a react component where a fetch call occurs. The component:
class SearchResults extends React.Component<{ tag: string; setSpinner: (value: boolean) => void }> {
state: searchResultsState = {
results: [],
tag: '',
cardFull: null,
};
constructor(props: { tag: string; setSpinner: (value: boolean) => void }) {
super(props);
this.handleCardModal = this.handleCardModal.bind(this);
}
componentDidMount() {
getResponse(
this.props.tag,
(res) => {
this.setState({ results: res, tag: this.props.tag });
},
this.props.setSpinner
);
}
componentDidUpdate(prevProps: { tag: string }) {
if (this.props.tag !== prevProps.tag) {
getResponse(
this.props.tag,
(res) => this.setState({ results: res, tag: this.props.tag }),
this.props.setSpinner
);
}
}
handleCardModal() {
this.setState({ cardFull: null });
}
render() {
return (
<Fragment>
{this.state.cardFull && (
<CardFull data={this.state.cardFull} handleClick={this.handleCardModal} />
)}
{this.state.cardFull && <div className="blackout" onClick={this.handleCardModal} />}
<div className="search-results">
{this.state.results.length === 0 && this.state.tag !== '' && <div>Sorry, no matched</div>}
{this.state.results.map((result) => (
<CardUI
key={result.id}
className="card"
variant="outlined"
sx={{ minWidth: 275 }}
onClick={() => {
const currentCard = this.state.results.filter((res) => res.id === result.id)[0];
this.setState({ ...this.state, cardFull: currentCard });
}}
>
<CardMedia component="img" height="194" image={getUrl(result)} alt={result.title} />
<CardContent>
<p>{result.title}</p>
</CardContent>
</CardUI>
))}
</div>
</Fragment>
);
}
}
First I tried to use jest-fetch-mock.
import '#testing-library/jest-dom';
import { render } from '#testing-library/react';
import renderer from 'react-test-renderer';
import SearchResults from '../../src/components/SearchResults/SearchResults';
import sampleSearchResults from '../__fixtures__/sampleSearchResults';
import fetch from 'jest-fetch-mock';
fetch.enableMocks();
beforeEach(() => {
fetch.resetMocks();
});
const setSpinner = jest.fn();
describe('Search Results component', () => {
fetch.mockResponseOnce(JSON.stringify({ photos: sampleSearchResults }));
test('Search Results matches snapshot', () => {
const searchResults = renderer
.create(<SearchResults tag={''} setSpinner={setSpinner} />)
.toJSON();
expect(searchResults).toMatchSnapshot();
});
test('search results renders correctly', () => {
render(<SearchResults setSpinner={setSpinner} tag={'dove'} />);
});
});
But it gives the error during tests:
console.error
FetchError {
message: 'invalid json response body at reason: Unexpected end of JSON input',
type: 'invalid-json'
}
So, I've decided to mock fetch manually
import React from 'react';
import '#testing-library/jest-dom';
import { render, screen } from '#testing-library/react';
import renderer from 'react-test-renderer';
import SearchResults from '../../src/components/SearchResults/SearchResults';
import sampleSearchResults from '../__fixtures__/sampleSearchResults';
const setSpinner = jest.fn();
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ photos: sampleSearchResults }),
})
) as jest.Mock;
describe('Search Results component', () => {
test('Search Results matches snapshot', () => {
const searchResults = renderer
.create(<SearchResults tag={''} setSpinner={setSpinner} />)
.toJSON();
expect(searchResults).toMatchSnapshot();
});
test('search results renders correctly', () => {
render(<SearchResults setSpinner={setSpinner} tag={'dove'} />);
const title = screen.getByText(/Eurasian Collared Dove (Streptopelia decaocto)/i);
expect(title).toBeInTheDocument(); //mistake
});
});
Now fetch mock works correct, but it renders only one div - search results and doesn't render card. How can I test my component? Thank you.
I'm getting this resize error when I get out the tab then get back:
I did notice that this happens when I call FitAddon.Fit() inside my ResizeObserver. I was using onSize to call .fit() but I did notice that when I used <Resizable>, the onSize event didn't fire so I went to use ResizeObserver and called .fit() from ResizeObserver's callback so the resize error begun.
my code look like this:
import { useEffect, useRef } from "react";
import { Terminal as TerminalType } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { WebLinksAddon } from 'xterm-addon-web-links';
import 'xterm/css/xterm.css';
export const Terminal = () => {
const id = 'xterm-container';
useEffect(() => {
const container = document.getElementById(id) as HTMLElement;
const terminal = new TerminalType({
cursorBlink: true,
cursorStyle: window.api.isWindows ? "bar" : "underline",
cols: DefaultTerminalSize.cols,
rows: DefaultTerminalSize.rows
});
const fitAddon = new FitAddon();
terminal.loadAddon(fitAddon);
terminal.loadAddon(new WebLinksAddon());
const resizeObserver = new ResizeObserver(entries => {
fitAddon.fit();
});
resizeObserver.observe(container);
terminal.open(container);
terminal.onData(key => {
window.api.send('terminal.data', key);
});
terminal.onResize(size => {
window.api.send('terminal.resize', {
cols: size.cols,
rows: size.rows
});
});
fitAddon.fit();
return () => {
resizeObserver.unobserve(container);
}
}, []);
return (
<div
id={id}
style={{height: '100%',
width: '100%',
}}
>
</div>
)
}
Hi we have purchased a theme. In the theme they have include JWT based login but used fake db(dummy values) and local storage. I want to change it to our logic which uses JWT based authentication. Backend used is spring boot.
I could not fully understand the code here. In the docs, they mentioned to make changes in the authcontext file only. However, it didnt worked after I made changes in the handlelogin function(authcontext.js file). Also I gave the loginEndpoint as the backend API URL, but not sure what to replace in place of meEndpoint. If anybody gets any better idea, please try to help. Will it work properly if me make changes in the handleLogin function of authContext.js file alone? or do we have to make changes in the initAuth async function that is defined inside useEffect hook? Also what is the onSubmit function(the function which executes when login button is clicked) doing
please find the code in AuthContext.js file
import { createContext, useEffect, useState } from 'react'
// ** Next Import
import { useRouter } from 'next/router'
// ** Axios
import axios from 'axios'
// ** Config
import authConfig from 'src/configs/auth'
// ** Defaults
const defaultProvider = {
user: null,
loading: true,
setUser: () => null,
setLoading: () => Boolean,
isInitialized: false,
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
setIsInitialized: () => Boolean,
register: () => Promise.resolve(),
token:null,
setToken:()=>null
}
const AuthContext = createContext(defaultProvider)
const AuthProvider = ({ children }) => {
// ** States
const [user, setUser] = useState(defaultProvider.user)
const [loading, setLoading] = useState(defaultProvider.loading)
const [isInitialized, setIsInitialized] = useState(defaultProvider.isInitialized)
const [token,setToken]=useState(defaultProvider.token)
// ** Hooks
const router = useRouter()
useEffect(() => {
const initAuth = async () => {
setIsInitialized(true)
const storedToken = window.localStorage.getItem(authConfig.storageTokenKeyName)
if (storedToken) {
setLoading(true)
await axios
.get(authConfig.meEndpoint, {
headers: {
Authorization: storedToken
}
})
.then(async response => {
setLoading(false)
setUser({ ...response.data.userData })
})
} else {
setLoading(false)
}
}
initAuth()
}, [])
const handleLogin = (params, errorCallback) => {
axios
.post(authConfig.loginEndpoint, params)
.then(async res => {
window.localStorage.setItem(authConfig.storageTokenKeyName, res.data.accessToken)
})
.then(() => {
axios
.get(authConfig.meEndpoint, {
headers: {
Authorization: window.localStorage.getItem(authConfig.storageTokenKeyName)
}
})
.then(async response => {
const returnUrl = router.query.returnUrl
setUser({ ...response.data.userData })
await window.localStorage.setItem('userData', JSON.stringify(response.data.userData))
const redirectURL = returnUrl && returnUrl !== '/' ? returnUrl : '/'
router.replace(redirectURL)
})
})
.catch(err => {
if (errorCallback) errorCallback(err)
})
}
const values = {
user,
loading,
setUser,
setLoading,
isInitialized,
setIsInitialized,
login: handleLogin,
logout: handleLogout,
register: handleRegister
}
return <AuthContext.Provider value={values}>{children}</AuthContext.Provider>
}
export { AuthContext, AuthProvider }
the authConfig file as below:-
export default {
meEndpoint: '/auth/me',
loginEndpoint: 'qortex-dev-backoffice-portal.westus2.cloudapp.azure.com:8080/auth-user/login',
registerEndpoint: '/jwt/register',
storageTokenKeyName: 'accessToken'
}
the handlesubmit function executes on login button click as shown below
const onSubmit = data => {
const { email, password } = data
auth.login({ email, password }, () => {
setError('email', {
type: 'manual',
message: 'Email or Password is invalid'
})
})
}
Login form controls code as shown below
<form noValidate autoComplete='off' onSubmit={handleSubmit(onSubmit)}>
<FormControl fullWidth sx={{ mb: 4 }}>
<Controller
name='email'
control={control}
rules={{ required: true }}
render={({ field: { value, onChange, onBlur } }) => (
<TextField
autoFocus
label='Email'
value={value}
onBlur={onBlur}
onChange={onChange}
error={Boolean(errors.email)}
placeholder='admin#materio.com'
/>
)}
/>
{errors.email && <FormHelperText sx={{ color: 'error.main' }}>{errors.email.message}</FormHelperText>}
</FormControl>
<FormControl fullWidth>
<InputLabel htmlFor='auth-login-v2-password' error={Boolean(errors.password)}>
Password
</InputLabel>
<Controller
name='password'
control={control}
rules={{ required: true }}
render={({ field: { value, onChange, onBlur } }) => (
<OutlinedInput
value={value}
onBlur={onBlur}
label='Password'
onChange={onChange}
id='auth-login-v2-password'
error={Boolean(errors.password)}
type={showPassword ? 'text' : 'password'}
endAdornment={
<InputAdornment position='end'>
<IconButton
edge='end'
onMouseDown={e => e.preventDefault()}
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOutline /> : <EyeOffOutline />}
</IconButton>
</InputAdornment>
}
/>
)}
/>
{errors.password && (
<FormHelperText sx={{ color: 'error.main' }} id=''>
{errors.password.message}
</FormHelperText>
)}
</FormControl>
I have a react-in-rails application that utilizes the google-maps-react api. The app works fine locally but when deployed to heroku, any component that imports google-maps-react does not render. Since this is generally the landing page for most users, the app is not accessible at all.
When all the components that import or render google-maps-react are removed, the app deploys correctly.
import React from "react"
import MapContainer from "./MapContainer"
import StoreList from './StoreList'
class FindBar extends React.Component {
render () {
const {stores, openTab, success} = this.props
return (
<div className="findbar" >
<div className="mapcomponent">
<MapContainer
stores={stores}
openTab={openTab}
success={success}
/>
</div>
<br/>
<StoreList
stores={stores}
openTab={openTab}
/>
{this.props.success &&
<Redirect to="/user_home/opentabs" />
}
</div>
);
}
}
export default FindBar
import React, { Component } from 'react';
import { Button, Card } from 'reactstrap';
import { Map, GoogleApiWrapper, Marker, InfoWindow } from 'google-maps-react';
import UserHome from './UserHome.js'
import StoreMarkerWindow from './StoreMarkerWindow.js'
import InfoWindowEx from './InfoWindowEx.js'
const mapStyles = {
width: '100%',
height: '100vh',
};
class MapContainer extends Component {
constructor(props) {
super(props)
this.state = {
showingInfoWindow: false,
activeMarker: {},
selectedPlace: {},
address: [],
location: {},
displayMarkers: [],
success: false,
}
}
componentDidMount = () => {
this.fetchMarkers()
}
componentDidUpdate = (prevProps) => {
if (prevProps.stores === this.props.stores){
return true
}
this.fetchMarkers()
}
openTab = () => {
console.log(this.state.selectedPlace.storeId)
// this.props.openTab(this.state.selectedPlace.storeId)
}
onClick = (props, marker, e) => {
this.setState({
selectedPlace: props,
activeMarker: marker,
showingInfoWindow: true
})
}
onClose = props => {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null
});
}
}
fetchMarkers = () => {
const newMarkers = []
this.props.stores.map((store, index) => {
const location = `${store.address1}, ${store.city}, ${store.state}, ${store.zip}`
this.geocodeAddress(location)
.then((geoco)=>{
newMarkers.push({lat: geoco.lat,
lng: geoco.lng,
storeId: store.id,
name: store.establishmentname,
location: location,
info: store.additionalinfo,
})
this.setState({ displayMarkers:newMarkers})
})
})
}
// create a function that maps stores.address, stores.city, stores.state, stores.zipcode
// and returns it to the geocodeAddress and then geocodeAddress returns it to
// the displayMarkers
geocodeAddress = (address) => {
const geocoder = new google.maps.Geocoder()
return new Promise((resolve, reject) => {
geocoder.geocode({'address': address}, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
resolve(results[0].geometry.location.toJSON())
} else {
reject()
}
})
})
}
render() {
const{
activeMarker,
showingInfoWindow,
selectedPlace,
onMapOver,
}=this.props
return (
<div className="mapContainer" style={mapStyles}>
<Map
google={this.props.google}
onMouseover={this.onMapOver}
zoom={14}
style={mapStyles}
initialCenter={{
lat: 32.7091,
lng: -117.1580
}}
>
{this.state.displayMarkers.map((coordinates, index) => {
const{storeId, lat, lng, name, location, info} = coordinates
return (
<Marker onClick={this.onClick}
key={index}
id={storeId}
name={name}
position = {{lat, lng}}
location={location}
info= {info}
>
</Marker>
)
})}
<InfoWindowEx
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
onClose={this.onClose}
>
<div>
<StoreMarkerWindow
name={this.state.selectedPlace.name}
location={this.state.selectedPlace.location}
info={this.state.selectedPlace.info}
id={this.state.selectedPlace.id}
openTab={this.props.openTab}
/>
</div>
</InfoWindowEx>
</Map>
</div>
);
}
}
export default GoogleApiWrapper({
apiKey: 'xxxx'
})(MapContainer);
TypeError: t is not a function
at Object.a (windowOrGlobal.js:18)
at Object.<anonymous> (windowOrGlobal.js:5)
at Object.<anonymous> (windowOrGlobal.js:5)
at n (bootstrap:19)
at Object.<anonymous> (ScriptCache.js:3)
at n (bootstrap:19)
at Object.<anonymous> (GoogleApiComponent.js:5)
at n (bootstrap:19)
at Object.<anonymous> (index.js:5)
at n (bootstrap:19)
This API returns a JSON array of objects, but I'm having trouble with setState and appending. Most documentation covers JSON objects with keys. The error I get from my renderItems() func is:
ItemsList.js:76 Uncaught TypeError: Cannot read property 'map' of undefined
in ItemsList.js
import React, { Component } from "react";
import NewSingleItem from './NewSingleItem';
import { findDOMNode } from 'react-dom';
const title_URL = "https://www.healthcare.gov/api/index.json";
class ItemsList extends Component {
constructor(props) {
super(props);
this.state = {
// error: null,
// isLoaded: false,
title: [],
url: [],
descrip: []
};
}
componentDidMount() {
fetch(title_URL)
.then(response => {
return response.json();
})
.then((data) => {
for (let i = 0; i < data.length; i++) {
this.setState({
title: data[i].title,
url: data[i].url,
descrip: data[i].bite,
});
console.log(data[i])
}
})
.catch(error => console.log(error));
}
renderItems() {
return this.state.title.map(item => {
<NewSingleItem key={item.title} item={item.title} />;
});
}
render() {
return <ul>{this.renderItems()}</ul>;
}
}
export default ItemsList;
Above, I'm trying to map through the items, but I'm not quite sure why I cant map through the objects I set in setState(). Note: even if in my setState() I use title: data.title, it doesnt work. Can someone explain where I'm erroring out? Thanks.
in App.js
import React, { Component } from "react";
import { hot } from "react-hot-loader";
import "./App.css";
import ItemsList from './ItemsList';
class App extends Component {
render() {
return (
<div className="App">
<h1> Hello Healthcare </h1>
<ItemsList />
<article className="main"></article>
</div>
);
}
}
export default App;
in NewSingleItem.js
import React, { Component } from "react";
const NewSingleItem = ({item}) => {
<li>
<p>{item.title}</p>
</li>
};
export default NewSingleItem;
The problem is this line:
this.setState({
title: data[i].title,
url: data[i].url,
descrip: data[i].bite,
});
When you state this.state.title to data[i].title, it's no longer an array. You need to ensure it stays an array. You probably don't want to split them up anyway, just keep them all in a self contained array:
this.state = {
// error: null,
// isLoaded: false,
items: [],
};
...
componentDidMount() {
fetch(title_URL)
.then(response => {
return response.json();
})
.then((data) => {
this.setState({
items: data.map(item => ({
title: item.title,
url: item.url,
descrip: item.bite,
})
});
console.log(data[i])
}
})
...
renderItems() {
return this.state.items.map(item => {
<NewSingleItem key={item.title} item={item.title} />;
});
}