How do I setState for a nested object in React? - javascript

I'm learning react by building a weather api. I make an API call and store it in state.
state = {
forecasts: {
error: null,
isLoaded: false,
forecasts: []
}
}
componentDidMount() {
const endpoint = `http://dataservice.accuweather.com/forecasts/v1/daily/5day/207931?apikey=KEY&language=en&details=true&metric=true`;
fetch(endpoint)
.then(res => res.json())
.then((result) => {
this.setState({
'forecasts.isLoaded': true,
'forecasts.forecasts': result.DailyForecasts,
});
},
(error) => {
this.setState({
'forecasts.isLoaded': true,
'forecasts.error': error
});
})
}
When I pass this down as props, I get no data?
<WeatherOverview weather={this.state.forecasts}/>

Use spread syntax to copy the entire previous object and then override some of its keys. You should also use the form of setState that takes a function because you want to reference the previous value of state.forecasts:
.then((result) => {
this.setState(state => ({
forecasts: {
...state.forecasts,
isLoaded: true,
forecasts: result.DailyForecasts,
},
}));
},
(error) => {
this.setState(state => ({
forecasts: {
...state.forecasts,
isLoaded: true,
error: error,
},
}));
})
or you may want entirely new objects to wipe out the previous error state:
.then((result) => {
this.setState({
forecasts: {
error: null,
isLoaded: true,
forecasts: result.DailyForecasts,
},
});
},
(error) => {
this.setState(state => ({
forecasts: {
forecasts: [],
isLoaded: true,
error: error,
},
}));
})

you are not passing the state correctly, you need to pass the state without quotation marks
this.setState({
'forecasts.isLoaded': true,
'forecasts.forecasts': result.DailyForecasts,
});
should be like this:
this.setState({
forecasts: {
...state.forecasts,
isLoaded:true,
forecasts:result.DailyForecasts},
});

Related

How to use react-toastify promise in axios

// how can I use the promise of toastify like I want to show spinner while fetching data then message success or failed
// but I am getting error in bellow code
const fetch = () => {
axios
.get("https://restcountries.com/v2/name/india")
.then((res) => {
toast.promise({
pending:"pending",
success:"success",
error:"rejected"
} )
console.log(res);
})
.catch((err) => {
toast.error("🦄 failed", {
position: "top-center",
autoClose: 2000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined
});
});
};
According to toast API https://fkhadra.github.io/react-toastify/promise/ the syntax should be
const myPromise = fetchData();
toast.promise(myPromise, {
loading: 'Loading',
success: 'Got the data',
error: 'Error when fetching',
})
An example which can be found on https://codesandbox.io/s/twilight-bash-jzs24y?file=/src/App.js
export default function App() {
const myPromise = new Promise((resolve) =>
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((json) => setTimeout(() => resolve(json), 3000))
// setTimeout just for the example , cause it will load quickly without it .
);
useEffect(() => {
toast.promise(myPromise, {
pending: "Promise is pending",
success: "Promise Loaded",
error: "error"
});
}, []);
return (
<div className="App">
<ToastContainer />
</div>
);
}
If you are not using promise. Use toast.loading.
(DOCS: https://fkhadra.github.io/react-toastify/promise/#toastloading)
const getData = () => {
const id = toast.loading("Please wait...")
axios.get(`some-url`)
.then(res => {
toast.update(id, {render: "All is good", type: "success", isLoading: false});
}).catch(err => {
toast.update(id, {render: "Something went wrong", type: "error", isLoading: false });
});
}
If it is not working then store toast id in useRef and then it will work.
You can use toast.update (https://fkhadra.github.io/react-toastify/update-toast)
const toastId = useRef(null)
const fetch() => {
toastId.current = toast.loading("Loading...")
axios
.post(...)
.then(() => {
toast.update(toastId.current, {
render: "Your message...",
type: "success",
isLoading: "false"
}
})
.catch(() => {
toast.update(toastId.current, {
render: "Your message...",
type: "error",
isLoading: "false"
}
})
}

How to fetch data from API using id in reactJS?

How do I pass an ID from one API to another, and fetch the require data?
This is my code:
handleClick(e){
fetch("http://api.com/product_sub_categories?category_id")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
product_sub_categories: result.product_sub_categories
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
You can do using back tick.
handleClick(e){
fetch(`${BASE_PATH}/product_sub_categories?category_id=${e.target.value}`)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
product_sub_categories: result.product_sub_categories
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}

How to make two axios requests and save both responses in a hook?

I'm using Axios to make API calls to my backend. The problem is that I want to make a call, save the response in a hook, than make another call and save the response in the same hook. I must make the second call after receiving the response from the first one, since in my backend the second call listen to an EventEmmiter:
const [invoice, setInvoice] = useState({
loading: false,
error: false,
content: null,
paid: false
});
function createInvoice() {
setInvoice({ ...invoice, loading: true });
api
.post("/lightning/createinvoice", {
amount: values.amount
})
.then(response => {
setInvoice({
loading: false,
error: false,
content: response.data,
paid: false
});
return api.get("/lightning/invoicestatus", {
params: { id: response.data.id }
});
})
.then(response => {
if (response.data.status === "Confirmed")
setInvoice({ ...invoice, paid: true });
})
.catch(() => {
setInvoice({ loading: false, error: true, content: null });
});
}
This code works, however I get invoices.content: null. I suspect that setInvoice({ ...invoice, paid: true }); fails, as the invoice state doesn't have its most updated state.
How should I fix it?
Thanks in advance
I have made a cleaner, much readable approach rather than just promise callbacks. Let me know if you find any issue, as I am not sure about your actual API calls which I can test. But the code below should work irrespectively.
const [invoice, setInvoice] = useState({
loading: false,
error: false,
content: null,
paid: false
});
const createInvoice = async (api, values) => {
try {
setInvoice({ ...invoice, loading: true });
const firstResponse = await api.post("/lightning/createinvoice", {
amount: values.amount
});
setInvoice({
...invoice,
content: firstResponse.data
});
const secondResponse = await api.get("/lightning/invoicestatus", {
params: { id: firstResponse.data.id }
});
if (secondResponse.data.status === "Confirmed") {
setInvoice({ ...invoice, paid: true });
}
} catch (err) {
setInvoice({ loading: false, error: true, content: null });
}
};

Test case failing with undefined property passed over props

I have been learning react for a while and have been working on creating a pet project. My friend created a test case which tests out some notification message from a method. This method in turn will use a constant from another class.
Below notification component utilizes a set of props(especially the partner props) passed over from routes.js.
class Notification extends Component {
constructor(props) {
super(props);
this.state = {
orientation: "ltr",
services: {
"applications": [],
"eta": "",
"start": ""
},
statuses: {},
locale_date: new Date(),
modal: {
open: false,
header: null,
desription: null
},
// This shouldn't be hardcoded but there are issues with passing this in as a prop in Routes.js
partner: props.partner
}
this.refreshEndpoints();
}
refreshEndpoints = () => {
const ref = this;
axios
.get(this.state.partner.get_device_status_url)
.then(response => {
var statuses = response.data;
if((typeof statuses) !== 'object') return false;
ref.setState({
statuses: statuses
});
}).catch(error => {
});
}
handleCreateNotification = () => {
const ref = this;
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(ref.state.services)
};
adalApiFetch(fetch, this.state.partner.get_endpoint_notifications_banner, options)
.then(response => {
ref.setState({
modal: {
open: true,
header: "Success",
description: "Successfully Created Notification"
}
});
})
.catch(function (error) {
ref.setState({
modal: {
open: true,
header: "Error",
description: "Failed to Create Notification"
}
});
});
}
handleDeleteNotification = () => {
const ref = this;
const options = {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(ref.state.services)
};
adalApiFetch(fetch, this.state.partner.get_endpoint_notifications_banner, options)
.then(response => {
ref.setState({
modal: {
open: true,
header: "Success",
description: "Successfully Deleted Notification"
}
});
})
.catch(function (error) {
ref.setState({
modal: {
open: true,
header: "Error",
description: "Failed to Delete Notification"
}
});
});
}
In routes.js I have route for calling out the above component which passes the props for partner.
<ProtectedNotificationPage orientation={orientation} partner={PartnerOne}/>
ParnerOne.js:
export const get_endpoint_notifications_banner = "<some url>"
export const get_device_status_url = "<some url>"
<class components>
I want to utilize the above const in notification component. And I was able to accomplish that using props.partner inside the state method.
But below test case is failing due to undefined property which is strange. But the notification functionality completely works fine. clearing and adding notification has no issues.
describe('Notification component', () => {
it('handleCreateNotification - Success', async () => {
const wrapper = shallow(<Notification />);
await wrapper.instance().handleCreateNotification();
expect(wrapper.state().modal).toEqual(
{
open: true,
header: "Success",
description: "Successfully Created Notification"
}
);
});
it('handleDeleteNotification', async () => {
const wrapper = shallow(<Notification />);
await wrapper.instance().handleDeleteNotification();
expect(wrapper.state().modal).toEqual(
{
open: true,
header: "Success",
description: "Successfully Deleted Notification"
}
);
});
I apologize for my lack of knowledge.. But this is something I couldn't figure out over tutorials/blogs. And I really appreciate if anyone able to point out the issue or reference for fixing this.
I tried utilizing bind across methods, which is something I thought might fix. But didn't workout. Apart from that I also tried accessing the props directly
like this.props.partner.get_device_status_url.. And still test case were failing.
I would suggest the following:
Importing into Notification.js:
const { get_endpoint_notifications_banner, get_device_status_url } = '<path_to_file>'.
You can now access these variables directly inside Notification.js.
Test case was having some issue. When I passed the partner one as props to my test case. It fixed the issue. It was looking for missing props

NGRX state property disappears

I'm trying to get user info from database. In component I'm getting decoded id from service, then call the action which takes the id as parameter. It returns the user, there is response in network tab. The state property 'currentUser' is null all the time until it should change to response, then it disappears.
export interface State {
loading: boolean;
loggedIn: boolean;
currentUser: User;
}
const initialState: State = {
loading: false,
currentUser: null,
loggedIn: localStorage.getItem("token") ? true : false
};
case AuthActions.GET_USER_SUCCESS:
{
return {
...state,
loading: false,
loggedIn: true,
currentUser: action.user
};
}
#Effect()
getUserInfo$: Observable < Action > = this.actions$
.ofType(fromActions.GET_USER)
.pipe(map((action: fromActions.GetUser) => action.id),
concatMap(id => {
return this.authService.getUser(id);
})
)
.pipe(map((res: User) => ({
type: fromActions.GET_USER_SUCCESS,
payload: res
})));
}
Try it like this:
#Effect()
getUserInfo$: Observable<Action> = this.actions$
.ofType(fromActions.GET_USER)
.pipe(
map((action: fromActions.GetUser) => action.id),
concatMap(id =>
this.authService.getUser(id).pipe(
map((res: User) => ({
type: fromActions.GET_USER_SUCCESS,
payload: res
}))
)
)
);
What is the shape of your action class? I can see you dispatch an action in the shape of
{
type: fromActions.GET_USER_SUCCESS,
payload: res
}
but in your reducer you expect it to have a user property on it
case AuthActions.GET_USER_SUCCESS:
{
return {
...state,
loading: false,
loggedIn: true,
currentUser: action.user // <- try action.payload or action.payload.user,
// depending on what you get from the API
};
}
Also, try to shape your effect more like this:
#Effect()
getUserInfo$: Observable <Action> = this.actions$
.ofType(fromActions.GET_USER)
.pipe(
switchMap(({ id }) => this.authService.getUser(id)
.pipe(
map((res: User) => ({ type: fromActions.GET_USER_SUCCESS, payload: res }),
// Handling errors in an inner Observable will not terminate your Effect observable
// when there actually is an error
catchError(err => ({ type: fromActions.GET_USER_ERROR, payload: err })
)
)
);
Hope this helps a little :)

Categories