import React, { Component} from 'react';
import { Route } from 'react-router';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.state = {
league: {
teams: {
data: [],
loaded: false,
config: {
icon: true,
parentId: 'leftSideTreeView'
}
},
players: {
data: [],
loaded: false,
config: {
icon: true,
parentId: 'leftSideTreeView'
}
},
games: {
data: [],
loaded: false,
config: {
icon: true,
parentId: 'leftSideTreeView'
}
},
error: false
}
};
}
componentDidMount() {
this.getTeamsHandler();
}
getTeamsHandler = () => {
axios.get('/api/League/GetTeams')
.then((response) => {
let prevState = [...this.state.league.teams];
prevState.data = response.data;
prevState.loaded = true;
this.setState({ teams: prevState });
})
.catch((error) => {
this.setState({ error: error });
});
}
renderTeamsHandler = () => {
let games = this.state.league.games;
let content = null;
if (games.data.length > 0) {
content = games.data.map((team, index) => {
return <div key={index}>{team.teamName}</div>;
});
}
return content;
}
render() {
let Team = this.renderTeamsHandler();
return (
<div>
{Team}
</div>
);
}
}
export default App;
The Ajax call does set data to prevState.Data but by the time it gets to rendering it, the state is the same as before the Ajax call. It is very confused as this all looks correct to me. Is it potentially async issue? If that is the case, why previously what I've done calls like this and had no issue at all.
Thanks for any help in advance.
I suspect that there are two part of problems.
First,the setState in getTeamsHandler:
axios.get('/api/League/GetTeams')
.then((response) => {
let prevTeam = [...this.state.league.teams];
prevTeam.data = response.data;
prevTeam.loaded = true;
this.setState(prevState => ({
league: {
...prevState.league,
teams: prevTeam
}
})
})
.catch((error) => {
this.setState(prevState => ({
league: {
...prevState.league,
error: error
}
});
});
Second,I guess there are some mistakes in renderTeamsHandler.Fetch date and set them in team, but use group in renderTeamsHandler.And the group in state is .
renderTeamsHandler = () => {
let teams = this.state.league.teams;
let content = null;
if (teams.data.length > 0) {
content = teams.data.map((team, index) => {
return <div key={index}>{team.teamName}</div>;
});
}
return content;
}
Related
I know this question has been asked multiple times but I cannot seem to find an answer. I have a component named DynamicTable which renders JSON as a data table. It has been tested in multiple other pages and works correctly. Here I have put it into a React-Bootstrap tab container. The data pull works correctly but the page is not re-rendering when the fetch is complete.
Here is the code I am using
//RetailRequests.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import FormComponent from '../Elements/FormComponent';
import TabContainer from 'react-bootstrap/TabContainer';
import Tabs from 'react-bootstrap/Tabs';
import Tab from 'react-bootstrap/Tab';
import DynamicTable from '../Elements/DynamicTable';
const mapStateToProps = (state) => {
return {
RequestData: state.RetailRequests,
siteMap: state.siteMap
}
}
const mapDispatchToProps = (dispatch) => {
return {
Retail_Request_Fetch: () => { return dispatch(Retail_Request_Fetch()) },
Retail_Request_Insert: (data) => { return dispatch(Retail_Request_Insert(data)) },
Retail_Request_Delete: (id) => { return dispatch(Retail_Request_Delete(id)) },
Retail_Request_DeleteAll: () => { return dispatch(Retail_Request_DeleteAll()) }
}
}
class RetailRequests extends Component {
constructor(props) {
super(props);
var roles = props.siteMap.siteMapData.userRoles.toLowerCase();
this.state = {
showAdmin: roles.indexOf('admin') >= 0 || roles.indexOf('systems') >= 0
}
}
componentDidMount() {
this.props.Retail_Request_Fetch();
}
// ...
render() {
let rows = this.buildData();
let data = this.props.RequestData?this.props.RequestData.adminData:null;
return (
<div style={{ transform: 'translateY(10px)' }} >
<TabContainer>
<div className='col-md-10 offset-1' >
<Tabs defaultActiveKey='general' id='retail_reports_tab_container' >
<Tab eventKey='general' title='Enter New Request'>
<h1> Retail Requests</h1>
<FormComponent rows={rows} submit={this.submitFn} />
</Tab>
<Tab eventKey='admin' title='Admin' disabled={!this.state.showAdmin}>
<h1>Manager Data</h1>
<DynamicTable
data={data}
border="solid 1px black"
title={"Retail Requests Admin"}
/>
</Tab>
</Tabs>
</div>
</TabContainer>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(RetailRequests);
//RetailRequestsCreator.js
export const Retail_Request_Fetch = () => (dispatch, getState) => {
var init = JSON.parse(JSON.stringify(fetchInit()));//copy to not modify the original
var myReq = new Request(`${process.env.REACT_APP_HOST}/Retail_Request`, init);
dispatch({
type: ActionTypes.REQUESTS_LOADING
})
return fetch(myReq)
.then((response) => {
if (response.ok) {
return response;
}
else {
var error = new Error("Error " + response.statusText);
error.response = response;
throw error;
}
}, (error) => {
var err = new Error(error.message);
throw err;
})
.then((response) => { return response.json() })
.then((RequestData) => {
if (RequestData !== "False") {
console.log(RequestData)
dispatch({
type: ActionTypes.REQUESTS_LOADED,
payload: RequestData
})
}
else CurrentPage_Update({ componentId: 'NotAllowed' });
})
.catch((err) => {
dispatch({
type: ActionTypes.REQUESTS_FAILED,
payload: "Error: " + err.message
})
});
}
//RetailRequestReducer.js
import * as ActionTypes from '../ActionTypes';
export const retailRequests = (state = {
isLoading: true,
errMess: null,
currentPage: []
}, action) => {
switch (action.type) {
case ActionTypes.REQUESTS_LOADED:
return { ...state, isLoading: false, errMess: null, adminData: action.payload };
case ActionTypes.REQUESTS_LOADING:
return { ...state, isLoading: true, errMess: null, adminData: {} };
case ActionTypes.REQUESTS_FAILED:
return { ...state, isLoading: false, errMess: action.payload, adminData: null };
default:
return state;
}
}
I am sure that there is something simple in this but the only error I am getting is that the data I am using, this.props.RequestData, is undefined although after the fetch I am getting proper state change in Redux.
It looks like you have problem in mapStateToProps
const mapStateToProps = (state) => {
return {
RequestData: state.retailRequests, // use lower case for retailRequests instead of RetailRequests
siteMap: state.siteMap
}
}
I'm trying to fetch data from firebase, then update the state of the app with the results and display the data as a list in a list component.
Everything works except the final list component displays it and immediately becomes blank again. After debugging, I found out it doesn't manage to correctly map the state to the props but I couldn't figure out how to achieve this. Thanks in advance
PlantList.js
import React, { Component } from 'react';
import PlantSummary from './PlantSummary';
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { fetchMyPlants } from '../../store/actions/myPlantsActions'
var i =0;
class PlantList extends Component {
constructor(props) {
super(props);
this.state = { myPlants: []} ;
}
componentDidMount() {
console.log("componentDidMount() triggered & state",i,this.state);
console.log("componentDidMount() triggered & props ",i,this.props);
this.props.dispatch(fetchMyPlants());
}
render(){
i = i +1;
console.log("render()"+i,this.props,this.state);
const { myPlants } = this.props;
return(
<div className="plant-list section">
{myPlants && myPlants.map((plant) => {
return (
<Link to={'/plant/'+ plant.id}>
<PlantSummary plant={plant} key={plant.id} />
</Link>
)
})}
</div>
)
}
}
const mapStateToProps = (state) => {
console.log("mapStateToProps triggered",state);
return {
myPlants: state.myPlants.items
}
}
export default connect(mapStateToProps)(PlantList)
myPlantActions.js
export const FETCH_MY_PLANTS_BEGIN = 'FETCH_MY_PLANTS_BEGIN';
export const FETCH_MY_PLANTS_SUCCESS = 'FETCH_MY_PLANTS_SUCCESS';
export const FETCH_MY_PLANTS_FAILURE = 'FETCH_MY_PLANTS_FAILURE';
export const fetchMyPlantsBegin = () => ({
type: FETCH_MY_PLANTS_BEGIN
});
export const fetchMyPlantsSuccess = myPlants => ({
type: FETCH_MY_PLANTS_SUCCESS,
payload: { myPlants }
})
export const fetchMyPlantsFailure = err => ({
type: FETCH_MY_PLANTS_FAILURE,
payload: { err }
});
export const fetchMyPlants = () => {
return(dispatch, getState, { getFirestore }) => {
dispatch(fetchMyPlantsBegin());
const firestore = getFirestore();
const authID = getState().firebase.auth.uid;
const usersPlants = [];
firestore.collection('users').doc(authID).collection('myPlants').get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
firestore.collection('plants').doc(doc.data().id).get().then(
function(document) {
if (document.exists) {
const docToPushId = {id: doc.data().id};
let docToPush = {
...docToPushId,
...document.data()
};
usersPlants.push(docToPush);
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
}
);
});
}).then(myPlants => {
console.log("Dispatch happens now:",usersPlants);
dispatch(fetchMyPlantsSuccess(usersPlants));
return myPlants;
}).catch(error => dispatch(fetchMyPlantsFailure(error)));
}
};
myPlantsReducer.js
import {
FETCH_MY_PLANTS_BEGIN,
FETCH_MY_PLANTS_SUCCESS,
FETCH_MY_PLANTS_FAILURE
} from '../actions/myPlantsActions';
const initialState = {
items: [],
loading: false,
error: null
};
export default function myPlantsReducer(state = initialState, action) {
switch(action.type) {
case 'FETCH_MY_PLANTS_BEGIN':
return {
...state,
loading: true,
error: null
};
case 'FETCH_MY_PLANTS_SUCCESS':
return {
...state,
loading: false,
items: action.payload.myPlants
};
case 'FETCH_MY_PLANTS_FAILURE':
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
return state;
}
}
console logs
My GET request with axios returns back undefined in my console. All of my endpoints are good and
working from being tested with postman. My initial state go from pets: [] to pets: "". I think it's how I have my async await function set up to get the response data.
Here's the GET Component code
import React, {
Component
}
from 'react';
import axios from 'axios';
export default class ListPets extends Component {
constructor(props) {
super(props);
this.state = {
pets: [],
isLoaded: false,
}
}
componentDidMount = () => {
this.getPets();
};
getPets = async() => {
const res = await axios.get('http://localhost:5000/pets/');
const pets = res.data;
this.setState({
isLoaded: true,
pets: pets
});
console.log('Data has been received!');
console.log(pets.data)
return pets;
}
render() {
console.log('State: ', this.state);
const {
isLoaded,
} = this.state;
if (!isLoaded) {
return <div> Loading... </div>;
} else {
return (<div></div>);
}
}
}
app.get('/pets', function(req, res){
const resultArray = [];
client.connect(err => {
assert.equal(null, err);
console.log("Connected successfully to server");
const db = client.db(dbName);
const cursor = db.collection('pet').find({});
iterateFunc = (doc,err) => {
assert.equal(null, err);
resultArray.push(doc);
console.log(JSON.stringify(doc, null, 4));
if(err) {
console.log(err)
}
}
cursor.forEach(iterateFunc);
client.close();
res.render('index', {pets: resultArray});
});
});
Your code can only render a Loading message but if the problem is the empty string then my guess is your API is returning an empty string.
Here's your code, slightly modified but working with a JSONPlaceholder API: https://codesandbox.io/s/compassionate-faraday-h9xpg
export default class ListPets extends Component {
constructor(props) {
super(props);
this.state = {
pets: [],
isLoaded: false
};
}
componentDidMount = () => {
this.getPets();
};
getPets = async () => {
const res = await axios.get("https://jsonplaceholder.typicode.com/todos");
const pets = res.data;
this.setState({ isLoaded: true, pets: pets });
};
render() {
const { isLoaded, pets } = this.state;
if (!isLoaded) {
return <div>Loading...</div>;
}
return <>{pets && pets.map(pet => <div key={pet.id}>{pet.title}</div>)}</>;
}
}
I'm trying to do a basic API fetch and show that information onClick using a button called GENERATE. All it should do for now is show the first url in the json I receive.
Once that is achieved, I want it to show the next url on each click.
App.js
import React, { Component } from 'react';
import { ThemeProvider, createToolkitTheme } from 'internaltools/theme';
import { AppHeader } from 'internaltools/app-header';
const LIGHT_THEME = createToolkitTheme('light');
const DARK_THEME = createToolkitTheme('dark');
const API = 'https://hn.algolia.com/api/v1/search?query=';
const DEFAULT_QUERY = 'redux';
class App extends Component {
constructor(props) {
super(props);
this.state = {
hits: [],
isLoading: false,
error: null,
};
}
componentDidMount(){
this.setState({ isLoading: true });
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong with the API...');
}
})
.then(data => this.setState({ hits: data.hits[0], isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
const { hits, isLoading, error } = this.state;
return (
<>
<button onClick={hits.url}>GENERATE</button>
</>
);
}
}
Please help me find out why my button doesn't work. And how do I iterate over the urls on each click, i.e. show the next url from the json on each click. Thanks.
You should pass a function name to your onClick handler. Then in that function you can access the data you wanted.
enter code here
import React, { Component } from 'react';
import { ThemeProvider, createToolkitTheme } from 'internaltools/theme';
import { AppHeader } from 'internaltools/app-header';
const LIGHT_THEME = createToolkitTheme('light');
const DARK_THEME = createToolkitTheme('dark');
const API = 'https://hn.algolia.com/api/v1/search?query=';
const DEFAULT_QUERY = 'redux';
class App extends Component {
constructor(props) {
super(props);
this.state = {
hits: [],
isLoading: false,
error: null,
hitsCount: 0
};
this.handleClick = this.handleClick.bind(this);
}
componentDidMount(){
this.setState({ isLoading: true });
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong with the API...');
}
})
.then(data =>
this.setState({ hits: data.hits, hitsCount: 0 ,isLoading: false
}))
.catch(error => this.setState({ error, isLoading: false }));
}
handleClick(){
this.setState(prevState => ({ hitsCount: prevState.hitsCount + 1
}));
}
render() {
const { hits, hitsCount, isLoading, error } = this.state;
return (
<>
<div>
count: {hitsCount}
url: {hits[hitsCount].url}
</div>
<button onClick={this.handleClick}>GENERATE</button>
</>
);
}
}
You need to pass an onClick handler function to update a state value.
Here's a codesandbox that stores the hits array in state along with a current index, and a handler that simply increments the index.
Consider This:
Read through the comments in the code to get the updates.
class App extends Component {
constructor(props) {
super(props);
this.state = {
hits: [],
currentHit: 0, //add a state currentHit to hold the url that is displayed by now
isLoading: false,
error: null,
};
}
componentDidMount(){
this.setState({ isLoading: true });
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong with the API...');
}
})
.then(data => this.setState({ hits: data.hits, isLoading: false })) //Make hits array holding all the hits in the response instead of only the first one
.catch(error => this.setState({ error, isLoading: false }));
}
handleClick = () => {
this.setState(prevState => ({
currentHit: prevState.currentHit + 1,
}));
}
render() {
const { hits, isLoading, error, currentHit } = this.state;
// pass the handleClick function as a callback for onClick event in the button.
return (
<>
<p>{hits[currentHit].url}<p/>
<button onClick={this.handleClick.bind(this)}>GENERATE</button>
</>
);
}
}
Here is the working code, on each click next url will be shown.
codesandbox link
handleChange method can work if you want to append the url from array as well. Or you could just increment the index in this function.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
data: [],
index: 0
};
componentDidMount() {
this.setState({ isLoading: true });
fetch("https://reqres.in/api/users")
.then(response => {
if (response) {
return response.json();
} else {
throw new Error("Something went wrong with the API...");
}
})
.then(data => this.setState({ data: data.data }))
.catch(error => this.setState({ error }));
}
handleChange = () => {
let i =
this.state.index < this.state.data.length ? (this.state.index += 1) : 0;
this.setState({ index: i });
};
render() {
return (
<div className="App">
<span>
{this.state.data.length && this.state.data[this.state.index].avatar}
</span>
<button onClick={this.handleChange}>GENERATE</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
i am writing a test case for a component and it is using redux ,saga ,and selectors
The test case as follows
const initialState = {
fetching: false,
error: null,
StockProducts: [],
products: [],
filter: '5b438ae60599132cb8b64b67'
}
const mockStore = configureStore()
const store = mockStore(fromJS(initialState))
import Stock from '../Stock'
test('for onUpdate funcion', () => {
const props = {
setErrorPopUp: jest.fn(),
formSubmitAttempt: jest.fn()
}
const wrapper = shallow(<Stock {...props} store={store} />)
wrapper.instance().onUpdate({ quantity: 0 })
expect(props.setErrorPopUp).toHaveBeenCalled()
wrapper.instance().onUpdate({ quantity: 3 })
expect(props.formSubmitAttempt).toHaveBeenCalled()
})
However test failed and i am getting this error
TypeError: Cannot read property 'fetching' of undefined
12 | createSelector(
13 | selectStockDomain,
> 14 | ({ fetching }) => fetching
15 | )
16 |
17 | const StockError = () =>
That is from one of the selectors i have used for the component
What is the wrong with my test and why is this happening
And how can i resolved this
this is the stock component
// react core
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createStructuredSelector } from 'reselect'
// component imports
// Selcetors and actions
import StockActions from './actions'
import ToastActions from 'containers/Toasts/actions'
import {
StockFetching,
StockError,
StockProducts,
StockFilter,
Products,
userData
} from './selectors'
// partials
const mapStateToProps = createStructuredSelector({
fetching: StockFetching(),
error: StockError(),
stockProducts: StockProducts(),
products: Products(),
filter: StockFilter(),
userData: userData()
})
const mapDispatchToProps = dispatch => ({
getStockProducts: () => {
dispatch(StockActions.getStockAttempt())
},
getProducts: payload => {
dispatch(StockActions.getProductsAttempt(payload))
},
formSubmitAttempt: payload => {
dispatch(StockActions.formSubmitAttempt(payload))
},
deleteStock: payload => {
dispatch(StockActions.deleteStockAttempt(payload))
},
setErrorPopUp: payload => {
dispatch(
ToastActions.setToast(payload.message, payload.action, payload.time)
)
},
reset: () => {
dispatch(StockActions.reset())
}
})
class Stock extends Component {
state = {
openCard: false,
showCard: false,
confirmDelete: false
}
// to show product details
onCard = openCard => {
this.setState({
openCard
})
}
// to show and close add to stock option
_ShowCard = () => {
this.setState({
showCard: !this.state.showCard
})
}
// onDlete Stock
onDeleteStock = data => {
let {
props: { deleteStock },
state: { confirmDelete }
} = this
if (confirmDelete) {
deleteStock(data)
this.setState({
confirmDelete: false
})
} else {
this.setState({
confirmDelete: true
})
}
}
// update the products
handlesubmit = data => {
if (parseInt(data.quantity) < 1) {
this.props.setErrorPopUp({
message:
'You cannot set quantity to zero.Please enter valid qunatity and try again',
action: 'danger',
time: '5000'
})
} else {
this.props.formSubmitAttempt({
product: data.product.key,
quantity: data.quantity
})
this._ShowCard()
}
}
onUpdate = data => {
if (parseInt(data.quantity) < 1) {
this.props.setErrorPopUp({
message: 'You cannot set quantity to zero.Use delete actions',
action: 'danger',
time: '5000'
})
} else {
this.props.formSubmitAttempt({
product: data.id,
quantity: data.quantity
})
}
}
onCancelDelete = () => {
this.setState({
confirmDelete: false
})
}
onGetStock = () => {
let { getStockProducts, getProducts, userData } = this.props
getStockProducts()
getProducts({ user: userData._id })
}
componentDidMount () {
this.onGetStock()
}
render () {
let {
props: { stockProducts, error, products, fetching },
state: { openCard, showCard, confirmDelete },
onCard,
onGetStock,
_ShowCard,
handlesubmit,
onUpdate,
onDeleteStock,
onCancelDelete
} = this
return (
<KeyboardAvoidingWrapper fluid enabled={!showCard}>
UI GOES HERE ....
</KeyboardAvoidingWrapper>
)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Stock)
Thanks
When you are testing a Component, it's a good idea to test just the behaviour of the Component independent of your redux store. Because, internally the store passes in state to the Component as props.
1. export your Component
So your Component definition code becomes
class Stock extends Component {
to
export class Stock extends Component {
2. import just the Component without the Connect HOC
Like this
import { Stock } from '../Stock'
test('for onUpdate funcion', () => {
const props = {
setErrorPopUp: jest.fn(),
formSubmitAttempt: jest.fn()
}
const wrapper = shallow(<Stock {...props} />)
// modify your code to see how your component behaves with different props
wrapper.instance().onUpdate({ quantity: 0 })
expect(props.setErrorPopUp).toHaveBeenCalled()
wrapper.instance().onUpdate({ quantity: 3 })
expect(props.formSubmitAttempt).toHaveBeenCalled()
})