This is my Lists component. I'm trying to map through the lists array but it shows an error message of "Cannot read property map of undefined"
import { Link } from 'react-router-dom'
import axios from 'axios'
import apiUrl from './../../apiConfig'
const Lists = (props) => {
const [lists, setLists] = useState([])
useEffect(() => {
axios({
url: `${apiUrl}/lists`,
method: 'GET',
headers: {
'Authorization': `Token token=${props.user.token}`
}
})
.then(res => setLists(res.data.lists))
.catch(console.error)
}, [])
// const listsJsx = lists.map(list => (
// <li key={list._id}>
// <Link to={`/lists/${list._id}`}>{list.name}</Link>
// </li>
// ))
return (
<div>
<h4>Lists</h4>
<ul>
{lists && lists.length > 0
? lists.map(list => {
return <div key={list._id}><Link to={`/lists/${list._id}`}>{list.name}</Link></div>
})
: 'Loading...'}
</ul>
</div>
)
}
export default Lists
That is because res.data.lists is undefined. You could try something like this:
.then(res => setLists(res.data.lists ? res.data.lists || []))
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.
enter image description here Here the singleCard object has all the details about the product such as id, title, src etc. I can console log the singleCard as console.log(singleCard) which shows all the data passed from redux store , but I can't get access to its individual elements like singleCard.src or singleCard.title . I want to showcase the images by the help of the useState hook by setting the initialState to singleCard.src , how can I do that??
function Singlecard() {
const dispatch = useDispatch();
const { id } = useParams();
const productId = id;
const productDetails = useSelector((state) => state.productDetails);
const { loading, error, singleCard } = productDetails;
const [currentImg, setcurrentimg] = useState(singleCard.src);
console.log(currentImg);
useEffect(() => {
dispatch(detailsProduct(productId));
}, [dispatch, productId]);
return (
<div>
<div className="singlecard-wrapper">
<div className="singlecard">
{/* card left */}
<div className="product-img">
<div className="img-display">
<div className="img-showcase">
<img src={currentImg} height="552px" width="552px" />
</div>
</div>
<div className="img-select">
<div className="img-item">
<Link to="#">
<img
src="https://instagram.fccu3-1.fna.fbcdn.net/v/t51.2885-15/e35/121105212_197680348436281_873870113726337107_n.jpg?tp=1&_nc_ht=instagram.fccu3-1.fna.fbcdn.net&_nc_cat=101&_nc_ohc=nsNxf_rVgeUAX9KUkp-&edm=AP_V10EAAAAA&ccb=7-4&oh=6b955579988a6dcbef129b3ad606fecd&oe=60918B32&_nc_sid=4f375e"
onClick={(e) => setcurrentimg(e.target.src)}
/>
</Link>
</div>
</div>
</div>
</div>
</div>
on using singleCard.src in useState it shows cannot read property 'src' of undefined . Please help !
productAction:
export const detailsProduct = (productId) => async (dispatch) => {
dispatch({ type: PRODUCT_DETAILS_REQUEST, payload: productId });
try {
const response = await axios.get(`/cardUpload/${productId}`);
dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: response.data });
} catch (err) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload:
err.response && err.response.data.message
? err.response.data.message
: err.message,
});
}
};
productReducer:
export const detailsProduct = (productId) => async (dispatch) => {
dispatch({ type: PRODUCT_DETAILS_REQUEST, payload: productId });
try {
const response = await axios.get(`/cardUpload/${productId}`);
dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: response.data });
} catch (err) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload:
err.response && err.response.data.message
? err.response.data.message
: err.message,
});
}
};
store:
import { applyMiddleware, combineReducers, compose, createStore } from "redux";
import thunk from "redux-thunk";
import {
productDetailsReducer,
productListReducer,
} from "./reducers/productReducers";
const initialState = {};
const reducer = combineReducers({
productList: productListReducer,
productDetails: productDetailsReducer,
});
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancer(applyMiddleware(thunk))
);
export default store;
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'));
I have tried the solution in the link below but still can't figure it out. I'm a newbie in react and also JS. Can anyone help me out. ##
Cannot read property 'map' of undefined
const mapStateToProps = state =>{
return {
data:{
names: state.names,
ages: state.ages,
genders: state.genders
},
};
}; // This is the copy of array in Redux state, it comes from the reducer.
const ConnectedList = ({data})=>(
<ul>
{data.names.map(el => (
<li key={el.id}>{el.data.names}</li>
))}
{data.ages.map(el => (
<li key={el.id}>{el.data.ages}</li>
))}
{data.genders.map(el => (
<li key={el.id}>{el.data.genders}</li>
))}
</ul>
);
const List = connect(mapStateToProps)(ConnectedList);
export default List;
Here is the reducer
const initialState = {
data:{
names:[],
ages:[],
genders:[],
}
};
function rootReducer (state = initialState, action){
if(action.type === ADD_DATA){
return Object.assign({}, state, {
names: state.data.names.concat(action.payload),
ages: state.data.ages.concat(action.payload),
genders: state.data.genders.concat(action.payload)
});
}
Okay turn out i just need to return the data but not var inside the data...
const mapStateToProps = state =>{
return {
data: state.data
};
};
I'm trying to implement a sports betting filter, but am getting stuck when trying to dispatch a thunk to re-render the page based on a sport-specific key that I pass into the thunk. I have a dropdown list that has click listeners that dispatch a handler in the parent component (GamesList). I'm noticing my action creator (gotKey) keeps returning the default key('americanfootball_ncaaf') even when clicking on the NFL list item in the DropdownList component. Any help or direction is much appreciated. Thanks!
Redux Store
import axios from 'axios'
require('../../secrets')
const GOT_GAMES = 'GOT_GAMES'
const GOT_MONEYLINES = 'GOT_MONEYLINES'
const GOT_KEY = 'GOT_KEY'
const gotGames = games => ({type: GOT_GAMES, games})
const gotMoneyLines = games => ({type: GOT_MONEYLINES, games})
const gotKey = key => ({type: GOT_KEY, key})
const defaultSportKey = 'americanfootball_ncaaf'
const apiKey = process.env.API_KEY
export const getGames = (key = defaultSportKey) => async dispatch => {
try {
const {data} = await axios.get(
`https://api.the-odds-api.com/v3/odds/?apiKey=${apiKey}&sport=${key}®ion=us&mkt=spreads`
)
dispatch(gotGames(data))
dispatch(gotKey(key))
} catch (error) {
console.error(error)
}
}
export const getMoneyLines = (key = defaultSportKey) => async dispatch => {
try {
const {data} = await axios.get(
`https://api.the-odds-api.com/v3/odds/?apiKey=${apiKey}&sport=${key}®ion=us&mkt=h2h`
)
dispatch(gotMoneyLines(data))
dispatch(gotKey(key))
} catch (error) {
console.error(error)
}
}
const gamesState = {
spreadGames: [],
moneylineGames: [],
sportKey: ''
}
const gamesReducer = (state = gamesState, action) => {
// console.log('action' + action)
switch (action.type) {
case GOT_GAMES:
return {...state, spreadGames: action.games}
case GOT_MONEYLINES:
return {...state, moneylineGames: action.games}
case GOT_KEY:
return {...state, sportKey: action.key}
default:
return state
}
}
export default gamesReducer
Main GamesList component
import React from 'react'
import {connect} from 'react-redux'
import {getGames, getMoneyLines} from '../store/games'
import SingleGame from './SingleGame'
import DropdownList from './DropdownList.js'
class GamesList extends React.Component {
constructor() {
super()
this.handler = this.handler.bind(this)
}
componentDidMount() {
this.props.getGames()
this.props.getMoneyLines()
}
handler(key) {
this.props.getGames(key)
this.props.getMoneyLines(key)
}
render() {
// console.log(this.state)
const spreadList = this.props.spreadGames.data
const moneylineList = this.props.moneylineGames.data
if (!spreadList || !moneylineList) {
return <div>loading</div>
} else {
return (
<div>
<DropdownList handler={this.handler} />
{spreadList.map((game, index) => (
(Date.now().toString().slice(0, -3) < game.commence_time) ?
<div key={index}>
<SingleGame
spreads={game.sites[0]}
homeTeam={game.home_team}
teams={game.teams}
timeStart={game.commence_time}
moneylineGame={moneylineList[index]}
/>
</div> : null
))}
</div>
)
}
}
}
const mapStateToProps = state => ({
spreadGames: state.games.spreadGames,
moneylineGames: state.games.moneylineGames,
sportKey: state.games.sportKey
})
const mapDispatchToProps = dispatch => ({
getGames: () => dispatch(getGames()),
getMoneyLines: () => dispatch(getMoneyLines())
})
export default connect(mapStateToProps, mapDispatchToProps)(GamesList)
DropdownList Component
import React from 'react'
export default class DropdownList extends React.Component {
constructor(props) {
super()
this.state = {
listOpen: false
}
this.showList = this.showList.bind(this)
this.hideList = this.showList.bind(this)
}
showList(event) {
event.preventDefault()
this.setState({listOpen: true}, () => {
document.addEventListener('click', this.hideList)
})
}
hideList() {
this.setState({listOpen: false}, () => {
document.addEventListener('click', this.hideList)
})
}
render() {
return (
<div>
<div type="button" onClick={this.showList}>
Select A Sport
</div>
{this.state.listOpen ? (
<ul>
<li
onClick={() => {
this.props.handler('americanfootball_nfl')
}}
>
NFL
</li>
<li
onClick={() => {
this.props.handler('americanfootball_ncaaf')
}}
>
NCAA
</li>
</ul>
) : (
<li>test</li>
)}
</div>
)
}
}
You're not passing your key in mapDispatchToProps
const mapDispatchToProps = dispatch => ({
getGames: key => dispatch(getGames(key)), // <-- forward key
getMoneyLines: key => dispatch(getMoneyLines(key)) // <-- forward key
})
react-redux has a nicer api for this, you just pass your action creators directly
export default connect(mapStateToProps, { getGames, getMoneyLines })(GamesList)
https://react-redux.js.org/api/connect#example-usage