I don't know what does cause this, it sends new request almost every half a second. I was thinking it's because I call my action in render method but it's not, tried to call it in componentDidMount, the same result.
Here is the code:
Action:
export const getComments = () => dispatch => {
dispatch({
type: GET_COMMENTS
})
fetch(`${API_URL}/comments`,
{ method: 'GET', headers: {
'content-type': 'application/json'
}})
.then((res) => res.json())
.then((data) => dispatch({
type: GET_COMMENTS_SUCCESS,
payload: data
}))
.catch((err) => dispatch({
type: GET_COMMENTS_FAILED,
payload: err
}))
}
Since I need post id loaded before I call the comment action I put it in render method:
componentDidMount() {
const { match: { params }, post} = this.props
this.props.getPost(params.id);
}
render() {
const { post, comments } = this.props;
{post && this.props.getComments()}
return <div>
...
Here is the route:
router.get("/comments", (req, res) => {
Comment.find({})
.populate("author")
.exec((err, comments) => {
if (err) throw err;
else {
res.json(comments);
}
});
});
Your getComments() function is running during render. The dispatch used in the action is causing a re-render, causing getComments() to fire again, producing an infinite loop.
Instead of fetching comments in the render() function, you should instead fetch them in the componentDidMount lifecycle hook, then in the render function simply display the comments from props;
getComments() is invoking the http request, so it should be moved to componentDidMount lifecycle hoook.
This should work:
componentDidMount() {
const { match: { params } = this.props
this.props.getPost(params.id);
this.props.getComments()
}
render() {
const { post, comments } = this.props;
{post && comments}
return <div>
...
When the component has mounted, the params are retrieved from props.match and the Post and Comments are fetched. Then with redux, post and comments data is dispatched, and can be accessed in the connected component's render method.
Related
I'm trying to implement Redirect in my react js App so if API call returns status other that 200, I can redirect users to according pages. The problem is that Redirect doesn't work. My code so far:
function catchErr(res) {
try {
if (res.status === 200) {
return res.json();
} else if (res.status === 404) {
<Redirect to="/404" // doesn't redirect to this route
console.log("404") // prints 404
throw Error(res.status);
}
else {
if (res.ok) {
return res.data;
}
}
} catch (err) {
console.log(err);
}
}
export async function getData() {
let getParams = {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
};
const data = await fetch(url, obj)
.then((res) => catchErr(res))
.then((res) => {
return res.data;
});
return data;
}
My api call is a function in separate file, it's not react functional component so I can't use useState hook to store state or use useHistory(history.push) inside getData() function. At the moment api call works great, catchErr() console.loges 404 if res.status === 404 but doesn't redirect to "/404" as I need to. I can't figure out why Redirect wouldn't work in this case, please help.
You can't call JSX like that in the middle of a function. You'll need to refactor your code to handle the failures in a React component and either set some state to conditionally render a Redirect component into the DOM or access the history object to do an imperative redirect, i.e. history.replace.
Here's an example component using history.replace.
import { useHistory } from 'react-router-dom';
const MyComponent = () => {
const history = useHistory();
...
useEffect(() => {
const fetchData = async () => {
// set any loading state
try {
const response = await fetch(url, options);
// process response, throw error if STATUS 404
} catch(error) {
// handle any error responses and redirect
history.replace("/404");
} finally {
// clear any loading state
}
}
fetchData();
}, []);
...
return (
<div>My JSX</div>
)
};
I am consuming the data from this API, using useEffect. What works only the first time you open the component.
What I want, is that every time the data changes in the API. Change the component information.
When I use useIsFocused or useFocusEffect, I get the error: couldn't find a navigation object. is your component inside a screen in a navigator.
const [infoUser, setInfoUser] = useState([]);
const getNameUser = async () => {
try {
const accessToken = await AsyncStorage.getItem('#access_token');
/* console.log('Token', accessToken); */
axios
.get(
'https://exampleapi/api/cliente',
{
headers: { Authorization: `Bearer ${accessToken}` },
},
)
.then(function (response) {
// handle success
console.log('DADOS USER:', response.data.data.nomeCompleto);
const userData = response.data.data;
setInfoUser([userData]);
})
.catch(function (error) {
// handle error
console.log(error);
});
} catch (e) {
// error reading value
console.log('Erro de token', e);
}
};
useEffect(() => {
getNameUser();
}, []);
Using useEffect hook by passing empty array like this:
useEffect(() => {
getNameUser();
}, []);
Giving it an empty array acts like componentDidMount as in, it only runs once.
Giving it no second argument acts as both componentDidMount and
componentDidUpdate, as in it runs first on mount and then on every re-render
for example:
useEffect(() => {
getNameUser();
});
Does anyone know why this fetch continues to fire. I have also tried putting it inside a useEffect with no luck. It should only fire once to return once imdbID has loaded.
const WatchOnList = ({ imdbId }) => {
const [locations, setLocations] = useState([])
var headers = new Headers();
headers.append("x-api-key", "API_KEY")
var requestOptions = {
method: 'GET',
headers: headers,
crossDomain: true,
redirect: 'follow'
};
async function fetchData() {
const res = await fetch(`${awsApiUrl}?imdb_id=${imdbId}`, requestOptions);
res
.json()
.then((res) => {
setLocations(res)
console.log(locations)
})
.catch(error => console.log('error', error));
}
fetchData();
With the current structure, the request will fire on every re-render. Which will be quite often in a React app. useEffect is the right place for such a function. But there are some caveats:
You can't make useEffect async, you have to create an async function inside the hook instead and call it afterward.
useEffect will per default run on every update, so you have to tell it explicitly to only run once (like componentDidMount for class components). This can be done by passing an empty array as the second parameter. The hook watches parameters specified in this array and only updates when one of them changes. As it is empty, it only fires once on initialization.
This should work:
useEffect(() => {
async function fetchData() {
const res = await fetch(`${awsApiUrl}?imdb_id=${imdbId}`, requestOptions);
res
.json()
.then(res => {
setLocations(res);
console.log(locations);
})
.catch(error => console.log("error", error));
}
fetchData();
}, []);
Read more about the behavior of hooks here and here.
I am deleteing an object then immediately retrieving a list of available objects, and am hitting a race-esk problem.
DELETE requests are subject to a CORS Pre-Flight OPTIONS request, while the GET request is not. This means my intended
DELETE /things/21
GET /things/
becomes:
OPTIONS /things/21
GET /things
DELETE /things/21
And the result of the GET includes object 21.
I want to avoid adding artificial delays; is there any other way to ensure the DELETE happens first?
(The requests are triggered from completely different components of my react app)
Edit:
I have a component Things which renders a summary list of things, and a component Thing which renders a page of detail.
Thing includes a Delete button, which fires a delete and navigates to Things. By "fires a delete" I mean: triggers an ajax DELETE to /things/21 and deletes thing 21 from my local redux store.
Things has a componentWillMount which triggers a GET to retrieve the list of available things, when they arrive my redux reducer adds them all to its store.
Edit: example:
Redux action creators
export const deleteThing = thingId => ({
type: 'DELETE_THING',
payload: thingId
});
export const retrieveThings = () => ({
type: 'FETCH_THINGS'
});
"Reducer" responsible for API requests
export default store => next => action => {
switch (action.type) {
case 'DELETE_THING':
return deleteObj(store.dispatch, action.type, `/things/{action.payload}`, action.payload);
case 'FETCH_THINGS':
return getObj(store.dispatch, action.type, '/things/');
}
}
const getObj = (dispatch, action, url) => {
return sendRequest(dispatch, () => fetch(url)
.then(processResponse(dispatch, action))
.catch(handleError(dispatch, action))
);
};
const deleteObj = (dispatch, action, url, payload) => {
return sendRequest(dispatch, () => fetch(url, {
method: 'DELETE',
headers
})
.then(results => {
if (results.status >= 400) return handleError(dispatch, action)(results);
// DELETE doesn't return anything but reducers want to know what was deleted, so pass the payload
return dispatch({
type: `${action}_SUCCESS`,
payload
});
})
.catch(handleError(dispatch, action))
);
}
// Wraps a fetch request, triggering redux events on send/receive (regardless of outcome)
const sendRequest = (dispatch, fn) => {
dispatch({type: 'SENDING_REQUEST'});
const always = () => dispatch({type: 'RECEIVED_RESPONSE'});
return fn().then(always, always);
}
Reducer/store for Things
export default (state = initialState, action) => {
case 'DELETE_THING_SUCCESS':
return state.deleteIn(['byId'], action.payload);
}
React Components
class Thing {
render () {
return (
<div>
<h1>{props.thing.id}</h1>
<button onClick={this.props.deleteThing}>Delete</button>
</div>
);
}
deleteThing () {
this.props.triggerActionToSend
// Pretend this is `connect`ed to a redux store
this.props.deleteThing(this.props.id);
// AJAX to delete from server
fetch({
url: '/thing/' + this.props.id,
method: 'delete'
});
// Redirect to main list
this.props.navigate('/things/');
}
}
// Pretend this is a `connect`ed component that receives a `things` prop from a redux store
class Things {
componentWillMount () {
this.props.retrieveThings();
}
render () {
return (
<ul>
{this.props.things.map(x => <li>x.id</li>)}
</ul>
);
}
);
const App = () => (
<div>
<Route path="/things" component={Things} />
<Route path="/thing/:thingId" component={Thing} />
</div>
);
Change your navigation call to wait for fetch promise to resolve. Should probably do same with deleting from local store but that isn't the issue at hand
// AJAX to delete from server
fetch({
url: '/thing/' + this.props.id,
method: 'delete'
}).then(_=> this.props.navigate('/things/'));
I got Users and Places as two different databases where each user has may places.
I have BasicInfo component where at a time 1 place of the user has to be loaded.
ComponentWillMount of BasicInfo
componentWillMount() {
this.props.dispatch(fetchUser())
this.props.dispatch(fetchPlaces(1))
}
I need to pass user id in fetchPlaces, as of now I'm hardcoding, but how do I do with the results of fetchUser? Should I do in componentWillReceiveProps(nextProps) ? Or is there any other way of doing it?
componenetWillReceiveProps makes sense, but I was wondering what if it is a chain of events? May be depending on place id if I have to fetch some other data.
Actions :
export function fetchPlaces(user_id) {
return function(dispatch) {
axios.get("/getPlaces?user_id=" + user_id)
.then((response) => {
console.log(response);
dispatch({type: "FETCH_PLACES_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_PLACES_REJECTED", payload: err})
})
}
}
export function fetchPlace(place_id) {
return function(dispatch) {
axios.get("/getPlace?place_id=" + place_id)
.then((response) => {
console.log(response);
dispatch({type: "FETCH_PLACES_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_PLACES_REJECTED", payload: err})
})
}
}
It looks like you're already using redux-thunk? If so, you can return the promise from your axios.get() calls:
return axios.get(...);
...then you can do something like this (I'm guessing what your fetchUser and user might look like):
this.props.dispatch(fetchUser()).then(user => this.props.dispatch(fetchPlaces(user.id)))
You can use a thunk or a promise middleware. You can find the motivation and examples in the documentation.