Expected effect:
In componentDidMount () I download s and saves in the variabletimeId. If timeId is true, passthis.state.timeId to the loadTime () function to https://app/load-id/${id} and call this function. The data returned by this function is saved in the variable checkId. this.state.checkId transfers to theDetails component.
Problem: how to call the function loadId (), after receiving data in componentDidMount ()?
App
class App extends Component {
constructor (props) {
super(props);
this.state = {
checkId: '',
timeId: ''
}
}
componentDidMount() {
axios({
url: `https://app`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => {
this.setState({
timeId: res.data.id,
});
})
.catch(error => {
console.log(error);
})
}
loadId = (id) => { //id ---> this.state.timeId
axios({
url: `https://app/load-id/${id}`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
console.log(response);
this.setState({
checkId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render () {
return (
<div>
<Item
/>
<Details
checkId = {this.state.checkId}
/>
</div>
)
}
}
Details
class Details extends React.Component {
constructor(props) {
super(props);
this.state = {
task: ''
};
}
componentDidUpdate(previousProps, previousState) {
if (previousProps.checkId !== this.props.checkId) {
this.setState({
task: this.props.checkId
})
}
render() {
return (
<div >
</div>
);
}
}
You need to call loadId inside the then function.
axios({
url: `https://app`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => {
this.setState({
timeId: res.data.id,
});
this.loadId(res.data.id);
})
.catch(error => {
console.log(error);
})
You need to bind loadId() to set state and call it when request in componentDidMount() returns response:
class App extends Component {
constructor (props) {
super(props);
this.state = {
checkId: '',
timeId: ''
}
this.loadId = this.loadId.bind(this); // Bind here
}
componentDidMount() {
axios({
url: `https://app`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => {
this.setState({
timeId: res.data.id,
});
this.loadId(res.data.id); // Call loadId
})
.catch(error => {
console.log(error);
})
}
//...
}
Related
I'm really new to react and I have this
import Axios from "axios";
import { useAuth } from "react-oidc-context";
const ProductService = {
getProductList: () => {
return Axios({
method: "get",
url: "<myurl>",
headers: {
"Authorization": useAuth().user?.access_token
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
},
getProduct: (productId: string) => {
return Axios({
method: "get",
url: "<myurl>/" + productId,
headers: {
"Authorization": useAuth().user?.access_token
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
},
addClient: (data: any) => {
return Axios({
method: "post",
url: "<myurl>",
data: data,
headers: {
"Authorization": useAuth().user?.access_token
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
}
}
export default ProductService
Notice that I'm trying to use useAuth() in the Authorization header and I'm getting React Hook "useAuth" is called in function "getProductList" which is neither a React function component or a custom React Hook function.
In this case, what's the workaround so I can use useAuth() to get user token.
My Component
<Button type="submit"
onClick={() => {
ProductService.addClient(data)
.then(() => {
toggleModal();
});
}}>
Add
</Button>
Thanks
Hooks is a function that controls state management or life cycle methods of the React component through registered order. So, React Hooks are not available outside the component. Please refer to the Link.
Only Call Hooks at the Top Level. So, the getProductList should be changed as follows.
const getProductList = (access_token) => {
if (!access_token) throw new Error('No access_token');
return Axios({
method: "get",
url: "<myurl>",
headers: {
"Authorization": access_token
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
};
const YourReactComponent = () => {
const auth = useAuth();
useEffect(() => {
getProductList(auth?.user?.access_token)
.then(() => {
/* NEXT STEP */
})
}, [auth?.user?.access_token]);
return <>
Component Text.
</>
};
As per Hooks rule, we can use hooks only from React function components or custom Hooks.
In your scenario,
Create one React component.
Get value from "useAuth()" in above functional component.
Pass above the value to ProductService.getProductList(auth) as one of the parameter.
I hope you are calling ProductService from particular react component right. Get auth value from there and pass it to ProductService.getProductList(auth)
const ProductService = {
getProductList: (authToken: any) => {
return Axios({
method: "get",
url: "<myurl>",
headers: {
"Authorization": authToken
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
},
getProduct: (authToken: any, productId: string) => {
return Axios({
method: "get",
url: "<myurl>/" + productId,
headers: {
"Authorization": authToken
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
},
addClient: (authToken: any, data: any) => {
return Axios({
method: "post",
url: "<myurl>",
data: data,
headers: {
"Authorization": authToken
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
}
}
const TestReactFunctionalComponent = () => {
const auth = useAuth();
// use below calling wherever you want inside this component
ProductService.getProductList(auth.user?.access_token);
return(
// your compoent elements
)
};
I'm using ReactJs with Material-UI to display a table of Car components but never update after Creating, Editing or Deleting a row.
Next is the structure:
class MainCar extends React.Component {
constructor(props) {
super(props);
this.state = {
cars: []
};
this.apiUrl = "http://localhost:8080/api/v1/cars";
this.onCreate = this.onCreate.bind(this);
this.onUpdate = this.onUpdate.bind(this);
this.onDelete = this.onDelete.bind(this);
this.loadFromServer = this.loadFromServer.bind(this);
}
loadFromServer() {
fetch(this.apiUrl)
.then(response => response.json())
.then(json => {
this.setState({
cars: json.cars
});
});
}
onCreate(newCar) {
try {
const result =
fetch(this.apiUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(newCar)
});
} catch(e) {
console.error(e);
}
this.loadFromServer();
}
onUpdate(car, updatedCar) {
try {
const result =
fetch(car._links.self.href, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(updatedCar)
});
} catch(e) {
console.error(e);
}
this.loadFromServer();
}
onDelete(car) {
try {
const result =
fetch(car._links.self.href, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(car)
});
} catch(e) {
console.error(e);
}
this.loadFromServer();
}
render() {
return (
<CarsTable cars={this.state.cars}
onCreate={this.onCreate}
onUpdate={this.onUpdate}
onDelete={this.onDelete} />
);
}
}
class CarsTable extends React.Component {
constructor(props) {
super(props);
}
render() {
const cars = this.props.cars.map(car =>
<Car key={car._links.self.href}
car={car}
onUpdate={this.props.onUpdate}
onDelete={this.props.onDelete} />
);
return (
<Table>
<TableBody>
{cars}
</TableBody>
</Table>
);
}
}
class Car extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<TableRow>
<TableCell>{car.code}</TableCell>
<TableCell>{car.color}</TableCell>
</TableRow>
);
}
}
As you seen, MainCar has cars array on its state, but CarTable and Car have only properties.
When I log on render functions I see data has changed, however the view is not updated.
View only updates
When I press F5 to update page.
Or when I create, update or delete a new Row I see the previous change but not the last one.
I read that React re-render a component when state has changed. Should I set state for CarTable and Car components copying from props? How can I solve this problem?
Thanks in advance.
What is probably happening is that your GET request is returning before your POST/PUT/DELETE is complete. One way to resolve this is to make sure the GET is only fired after the other action has completed by putting it in a then block.
e.g.
onDelete(car) {
try {
const result =
fetch(car._links.self.href, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(car)
}).then(a => this.loadFromServer());
} catch(e) {
console.error(e);
}
}
You need to await asynchronous things in JavaScript. Otherwise, you're going to be doing your POST and GET in parallel instead of sequentially. You can use an async function to make this easier.
async onCreate(newCar) {
try {
await fetch(this.apiUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(newCar)
});
} catch(e) {
console.error(e);
}
return this.loadFromServer();
}
Your code looks like it should work (I know that's not the most helpful thing to hear when it clearly deosn't), but here's a few things you could try. Try doing bind(this) on your functions in the constructor, and also try calling this.loadFromServer(), not loadFromServer(). Also, I'm guessing you just left this out of your post but you're missing onDelete
See below (look at constructor and onCreate method):
class MainCar extends React.Component {
constructor(props) {
super(props);
this.loadFromServer = this.loadFromServer.bind(this);
this.onCreate = this.onCreate.bind(this);
this.onUpdate = this.onUpdate.bind(this);
this.state = {
cars: []
};
}
loadFromServer() {
fetch(this.apiUrl)
.then(response => response.json())
.then(json => {
this.setState({
cars: json.cars
});
});
}
onCreate() {
// more code to create...
this.loadFromServer(); // to update cars state
}
onUpdate() {
// more code to put...
this.loadFromServer(); // to update cars state
}
render() {
return (
<CarsTable cars={this.state.cars}
onCreate={this.onCreate}
onUpdate={this.onUpdate}
onDelete={this.onDelete} />
);
}
}
I have created the function getNewToken, in which I am fetching a new token on the basis of refresh_token and setting it in localstorage. Where to check the Unauthorized 401 error and call this function this.getNewToken()?
if (error.status === 401) {
this.getNewToken ();
}
axios.defaults.baseURL = localStorage.getItem('domain');
class App extends Component {
constructor (props) {
super(props);
this.state = {
items: []
}
}
componentDidMount() {
axios.defaults.baseURL = localStorage.getItem('domain');
axios({
url: "/api/v1/items",
method: "GET"
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
console.log(response.data)
this.setState({
items: response.data,
});
})
.catch(error => {
if(error.status === 401) {
console.log(error.status);
this.getNewToken();
}
})
}
getNewToken = () => {
axios.defaults.baseURL = localStorage.getItem('domain');
const url = '/api/refresh_token';
const data = qs.stringify({
grant_type: 'refresh_token',
client_secret: ****,
client_id: ****,
refresh_token: ****
});
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
axios({
method: 'post',
url,
data,
config
})
.then(res => {
if (res.status === 200) {
localStorage.setItem('token', JSON.stringify(res.data))
this.setState({
token: res.data
})
}
}).catch(err => {
console.error(err);
});
}
render () {
return (
<div>
</div>
)
}
}
I have a state which is an empty array:
constructor(props) {
super(props);
this.state = {
test_steps: [],
};
}
I need to fill up that state with the following data that get when I do a GET request:
See image
UPDATED:
export function copyTestScenarioLog(tSL) {
console.log("\nCalled copyTestScenarioLog");
let url = config.base_url + `/test_scenario_logs`;
return fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + getUserToken() },
body: JSON.stringify({
_id: tSL._id['$oid']
})
})
.then(res => res.json())
.then(data => {
getTestStepLogs(data)
return data;
})
.catch(err => console.log(err));
}
export function getTestStepLogs(data) {
const id = data.test_step_logs[0].$oid;
let url = config.base_url + `/test_step_logs/${id}`;
return fetch(url, {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + getUserToken() }
})
.then(res => res.json())
.then(data => {
console.log(data)
return data
})
.catch(err => console.log(err));
}
How do I update my state after doing a GET fetch?
This is full react component code, you see how I call your funciton in componentDidMount, and in here I pass 'this' as an argument to copyTestScenarioLog.
import React, { Component } from 'react';
import copyTestScenarioLog from './copyTestScenarioLog';
class Component1 extends Component {
constructor(props) {
super(props);
this.state = {
test_steps: []
};
}
componentDidMount() {
var reactComponent = this;
copyTestScenarioLog('i dont konw that is tsl', reactComponent);
}
render() {
return (
<div></div>
);
}
}
export default Component1;
In 'copyTestScenarioLog', I get that ( refers to react component), and use setState function in react.
export function copyTestScenarioLog(tSL, reactComponent) {
console.log("\nCalled copyTestScenarioLog");
let url = config.base_url + `/test_scenario_logs`;
return fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + getUserToken() },
body: JSON.stringify({
_id: tSL._id['$oid']
})
})
.then(res => res.json())
.then(data => {
getTestStepLogs(data)
reactComponent.setState({
test_steps: data
});
return data;
})
.catch(err => console.log(err));
}
But basically I don't use this approach, I just wanted to show that how its done, I usually use await/async or function generators, because they are better approaches. Search about them and learn to use them.
you can pass onSuccess function into your getTestStepLogs and update your state.
export function getTestStepLogs(data , onSuccess) {
const id = data.test_step_logs[0].$oid;
let url = config.base_url + `/test_step_logs/${id}`;
return fetch(url, {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + getUserToken() }
}).then(resp => {
if (onSuccess)
onSuccess(resp);
}).catch(err => console.log(err));
}
and when you call getStepLogs pass onSuccess as props:
this.props.getTestStepLogs(data , (resp)=>{
this.setState({test_steps:resp});
})
if you are using the get call at multiple place, you can be little generic and try this approach.
//return here does not do anything right now
export function getTestStepLogs(data) {
return new Promise(function(resolve, reject){
const id = data.test_step_logs[0].$oid;
let url = config.base_url + `/test_step_logs/${id}`;
return fetch(url, {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + getUserToken() }
})
.then(res => res.json())
.then(data => {
console.log(data)
resolve(data);
})
.catch(err => {console.log(err);
reject(err);
});
})
}
async componentDidMount() {
let data = await copyTestScenarioLog();
//now set it to state
}
For an async call, there are three states. Call initiation, call success and call failure. Say "isLoading" represents the status of the call being running in the background.
Initially =>
{
isLoading : false,
data : '',
err : ''
}
Call initiated =>
{
isLoading : true,
data : '',
err: ''
}
Call success =>
{
isLoading : false,
data : response.data
err: ''
}
Call failed =>
{
isLoading :false,
data : '',
err: err
}
Usually, the GET calls of a component are made in componentDidMount. It is also the suggested way as per the react documentation.
componentDidMount(){
//Call Initiation
this.setState({
isLoading : true,
data : '',
err: ''
});
makeApiCall(api).
then((response) => {
this.setState({
isLoading : false,
data : response.data
err: ''
});
},
(err) => {
this.setState({
isLoading :false,
data : '',
err: err
})
})
.catch((err) => {
this.setState({
isLoading :false,
data : '',
err: err
})
})
}
I'm able to loop through JSON data to create an array filled with numbers, but when I go to create the list items it doesn't work. The component just renders an empty list.
When I console.log(ticketNumbers) right before the map function, it shows as a collapsed Array [] until I expand it (it then shows all the values)
function apiCall() {
var ticketNumbers = [];
var memId = 'xxx';
var myInit = {
method: 'GET',
mode: 'cors',
headers: {
'authorization': "xxx",
'Access-Control-Allow-Origin':'*',
'content-type': "application/json",
'cache-control': "no-cache"
},
params: {
'orderBy': 'status/name asc',
'pageSize': 300,
'conditions': "resources contains '" + memId + "' AND status/id not in (17,165,36,163,164,42,73,46,78,148,34,132,45,159,60,168,106,51,72,95)"
}
};
axios.get('Url', myInit)
.then((response) => {
console.log(response.data)
for (var ticket in response.data) {
ticketNumbers.push(response.data[ticket].id)
};
})
return ticketNumbers
}
class TicketContainer extends Component {
constructor(props) {
super(props)
this.state = {
data: [],
loading: true,
};
}
componentWillMount() {
this.setState({
data: {
numbers: apiCall()
},
loading: false
})
};
render() {
return(
<div>
{this.state.loading ? 'Loading' : <Tickets data={this.state.data} />}
</div>
)
}
}
class Tickets extends Component {
render() {
const stuff = this.props;
var ticketList = stuff.data.numbers;
console.log(ticketList);
return(
<div>
<ul>Ticket Number
{ticketList.map((ticket, index) => {
return <li key={index}>sweet</li>;
})}
</ul>
</div>
);
}
}
You should correctly use Promise to solve this. First, let's change apiCall so that it will return a Promise:
function apiCall() {
var ticketNumbers = [];
var memId = 'xxx';
var myInit = {
method: 'GET',
mode: 'cors',
headers: {
'authorization': "xxx",
'Access-Control-Allow-Origin':'*',
'content-type': "application/json",
'cache-control': "no-cache"
},
params: {
'orderBy': 'status/name asc',
'pageSize': 300,
'conditions': "resources contains '" + memId + "' AND status/id not in (17,165,36,163,164,42,73,46,78,148,34,132,45,159,60,168,106,51,72,95)"
}
};
return axios.get('Url', myInit)
.then((response) => {
console.log(response.data)
for (var ticket in response.data) {
ticketNumbers.push(response.data[ticket].id)
}
return ticketNumbers;
});
}
You know have a Promise based api that can be used like this:
apiCall().then(ticketNumbers => console.log(ticketNumbers);
We just need to modify componentWillMount know:
componentWillMount() {
apiCall().then(numbers => this.setState({ loading: false, data: numbers });
}
The apiCall function calls an API which is asynchronous process and returning ticketNumbers from function request won't return the result as the return statement will be executed before the API response is ready and ticketNumbers array is populated.
The easiest way for you to do this is to define this function in the React class and directly setState in the callback of axios request
class TicketContainer extends Component {
constructor(props) {
super(props)
this.state = {
data: [],
loading: true,
};
}
componentWillMount() {
this.apiCall()
};
apiCall =() => {
var memId = 'xxx';
var myInit = {
method: 'GET',
mode: 'cors',
headers: {
'authorization': "xxx",
'Access-Control-Allow-Origin':'*',
'content-type': "application/json",
'cache-control': "no-cache"
},
params: {
'orderBy': 'status/name asc',
'pageSize': 300,
'conditions': "resources contains '" + memId + "' AND status/id not in (17,165,36,163,164,42,73,46,78,148,34,132,45,159,60,168,106,51,72,95)"
}
};
axios.get('Url', myInit)
.then((response) => {
var ticketNumbers = [];
for (var ticket in response.data) {
ticketNumbers.push(response.data[ticket].id)
};
this.setState({data: ticketNumbers, loading: false})
})
}
render() {
return(
<div>
{this.state.loading ? 'Loading' : <Tickets data={this.state.data} />}
</div>
)
}
}
class Tickets extends Component {
render() {
const stuff = this.props;
var ticketList = stuff.data.numbers;
console.log(ticketList);
return(
<div>
<ul>Ticket Number
{ticketList.map((ticket, index) => {
return <li key={index}>sweet</li>;
})}
</ul>
</div>
);
}
}
In case you are wondering why the console.log() statment logs the array, check this answer Value below was evaluated just now in JavaScript object