How do i update the rendered data of a react component? - javascript

I am making simple blog application, and i have a page for every individual post that is accessed this way:
http://localhost:3015/Post/post_id
And in the router i have set up the post component to render in this way:
<Route component={Post} path="/Post/post_id"></Route>
Then, in my post component i run the following code to make a call to the REST api im using:
componentDidMount(){
let id = this.props.match.params.post_id
axios.get('https://jsonplaceholder.typicode.com/posts/' + id)
.then(res => {this.setState({ //res is short for responce
post: res.data
})
})
this.setState({
id: id
})
}
And this works for the first post - when i click on a post it takes me to that individual post but after that, even though the URL changes, it doesnt update the component. I checked the state through the developer tools on chrome and realized that it doesn't update the state either.
Im new to react, and decided to take on this small project before using it full time, so excuse me if i made a rookie mistake. After all i am a rookie.

Component will re-render only if some of his props or state changes, and if it's already mounted, componentDidMount it's not called again. So, you can try to call the rest api through your componentDidUpdate method (when the match params changes).
componentDidUpdate() {
if (this.props.match.params.post_id != this.state.id) {
// make the http call and update id, and post state
}
}
https://reactjs.org/docs/react-component.html#componentdidmount

Related

Call api before first render in functional component in React.js

If I want to call API after the first rendering of component, I know we have useEffect hook to call the API method. (I am talking about functional components only. No class component).
Is there any way, I can call the API before my component renders the first time.
The reason for this question is, If some UI part is dependent on API, I do not want to show any incomplete information to the user on the first render also, which will be changed once I get the data from API.
This seems to be a bad experience with UI.
Edit: I got a couple of advice to use useLayoutEffect or any consumable flag to check if it is rendered or not. I have checked useLayoutEffect does not work, and by using the consumable flag, we are increasing the complexity only.
Do we have any better way for this?
I think useLayoutEffect can be used for something like this, by passing in an empty array as second argument. useLayoutEffect(() => {...}, []);
Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
Although you can always fetch the data in the parent component and pass it as props. Or - if you don't mind it being an experimental feature for now - React Suspense is trying to solve this exact problem.
There are no correct ways to make API call before component rendered from the same component.
You may preferred make API call in parent component and render presentation component when and only when have consumable data.
Another workaround for such case is keep consumable flag inside component, make request inside useEffect, render nothing or some kind loader and render something only when request completed with success.
on calling api it is not responding exact on its first render but giving exact response when it's being hit second time
You can have a spinner or loading component be rendered first conditionally (isLoading for example):
if(isLoading) return <Spinner />
and have the api call set (isLoading) to false on status 200 for example.
Just came across something, which may help someone in future. So we can use some library but the specific one I would mention here is React Query
React query does exactly what we are trying to achieve, the hooks like useQuery fetch data as soon as rendering starts so you don’t have to wait until react loads the entire component as follows
// with react query
const { status, data, error, isFetching } = useQuery(
['data'],
async () => {
const data = await (
await fetch(`${API_BASE_URL}/data`)
).json()
return data
}
)
// without react query
useEffect(() => {
try {
setLoading(true)(async () => {
const data = await (await fetch(`${API_BASE_URL}/data`)).json();
setData(data);
})();
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}, []);
Here is the article link if you want to read

React Router -- history push state not refreshing with new state object

When Promise.all resolves and the new activity is saved, the user should be routed to /activities to view their newly created activity. Everything works as expected, however I currently need to refresh /activities page (once) after being routed in order to view the new activity in the table.
const handleSaveActivity = e => {
e.preventDefault();
Promise.all([
addActivity(),
saveActivity()
]).then(() => {
props.history.push('/activities');
})
};
I'm not sure how to re-render the page automatically after pushing a new history state, so the user does not need to manually refresh the page to see the new state. Happy to provide more code snippets if I left out something critical.
Hi i must be a little late to answer this, but this issue can be due to the wrong use of useEffect, if you have lets say a todo list and you wanna fetch data with axios for example, it would look like this:
useEffect(()=>{
axios.get(`${YOUR_URL}/todos`)
.then((res)=>{
setTodos(todos=res.data)
})
},[])
now as you can see we have initial value of an empty array, so this is acting as a ComponentDidMount, what you might want is to re render the component after it gets a new value, so you want to have a ComponentDidUpdate effect, so you would just not initialize the value as an empty array, therefore it would look like this:
useEffect(()=>{
axios.get(`${YOUR_URL}/todos`)
.then((res)=>{
setTodos(todos=res.data)
})
})
Hope this helps someone, couse i landed here due to the same issue and came to solve it this way.
just to run this.setState({whateverKey:whateverValue})?
In your activities page (call it Activities component) you should call API to get the updated data every time browser hit this component URL.
With class based style, you should do it in componentDidMount life cycle hook
class Activities extends Component {
// ...
componentDidMount() { loadActivities() }
// ...
}
With function based style, you should do it in useEffect hook
import React, { useEffect } from 'react'
const Activities = () => {
useEffect(() => { loadActivities() });
}
https://github.com/supasate/connected-react-router Please use this package, it solves the problem.
This issue I've faced a few minutes ago...however I finally found the solution by manually using the vanilla javascript. => for refreshing the page you can use
=> window.location.reload(false); after using the push property.

Where to act on changed child properties in React JS

I'm confused, and I have searched a lot for the answer to this (seemingly) basic question.
I'm learning React, and I have a rather common component hierarchy with one top component (lets call it App) which contains a number of subcomponents (a grid, a graph, a table etc).
They all show information regarding one product.
Now when I select a row in the grid, I want to inform the other subcomponents about the change. App therefore passes a callback method
onSelectedProduct={this.onSelectedProduct}
to the grid. This gets called OK. In this App method I set the state:
onSelectedProduct(product) {
this.setState({ product: product });
}
In its render(), App has declared another subcomponent:
<ProductGraph product={this.state.product} />
Since ProductGraph needs to fetch some data asynchronously "to-be-rendered" later, where should I catch this property change??
The old "componentWillReceiveProps" sounded like the proper place, but will be deprecated and should not be used, I understand.
I have also tried shouldComponentUpdate, getDerivedStateFromProps and even to catch it in render, but they all have downsides and eventually lead to horrible code.
Somewhere, somehow, I should be able to detect that props.product !== state.product and issue an async load call for the data...
When the async method I call returns with the data, it will set the state and render itself.
So where is the optimal place to catch changed properties?
I have read a lot about the React Lifecycle but I just can't seem to find this basic information. Am I stupid or maybe blind? Or have I got this completely wrong somehow?
You are looking for componentDidUpdate() the lifecycle method that triggers when a component receives new props or has an updated state.
https://reactjs.org/docs/react-component.html#componentdidupdate
In your ProductGraph component, you would do something like:
componentDidUpdate(prevProps){
if(this.props.product !== prevProps.product){
fetch(`myApi/${this.props.product}`)
.then(res => res.json())
.then(data => this.setState({ data: data })) <-- identify what you need from object
.catch((errors) => {
console.log(errors)
})
}
}

React, Apollo 2, GraphQL, Authentication. How to re-render component after login

I have this code: https://codesandbox.io/s/507w9qxrrl
I don't understand:
1) How to re-render() Menu component after:
this.props.client.query({
query: CURRENT_USER_QUERY,
fetchPolicy: "network-only"
});
If I login() I expect my Menu component to re-render() itself. But nothing.
Only if I click on the Home link it re-render() itself. I suspect because I'm using this to render it:
<Route component={Menu} />
for embrace it in react-router props. Is it wrong?
Plus, if inspect this.props of Menu after login() I see loading: true forever. Why?
2) How to prevent Menu component to query if not authenticated (eg: there isn't a token in localStorage); I'm using in Menu component this code:
export default graphql(CURRENT_USER_QUERY)(Menu);
3) Is this the right way to go?
First, let me answer your second question: You can skip an operation using the skip query option.
export default graphql(CURRENT_USER_QUERY, {
skip: () => !localStorage.get("auth_token"),
})(Menu);
The problem now is how to re-render this component when the local storage changes. Usually react does not listen on the local storage to trigger a re-render, instead a re-render is done using one of this three methods:
The component's state changes
The parent of the component re-renders (usually with new props for the child)
forceUpdate is called on the component
(Note that also a change of a subscribed context will trigger a re-render but we don't want to mess around with context ;-))
You might want to go with a combination of 2. and 3. or 1. and 2.
In your case it can be enough to change the route (as you do in the login function). If this does not work you can call this.forceUpdate() on the App component after Login using a callback property on <Login onLogin={() => this.forceUpdate() } />.
EDITED
1) I just created new link https://codesandbox.io/s/0y3jl8znw
You want to fetch the user data in the componentWillReceiveProps method:
componentWillReceiveProps() {
this.props.client.query({query: CURRENT_USER_QUERY})
.then((response) => {
this.setState({ currentUser: response.data.currentUser})
})
.catch((e) => {
console.log('there was an error ', e)
})
}
This will make the component re-render.
2) Now when we moved the query call in the component's lifecycle method we have full control over it. If you want to call the query only if you have something in localstorage you just need to wrap the query in a simple condition:
componentWillReceiveProps() {
if(localstora.getItem('auth_token')) {
this.props.client.query({query: CURRENT_USER_QUERY})
.then((response) => {
this.setState({ currentUser: response.data.currentUser})
})
.catch((e) => {
console.log('there was an error ', e)
})
}
}
3) You want to store the global application state in redux store. Otherwise you will have to fetch the user info every time you need to work with it. I would recommend to define a state in you App component and store all the global values there.

React router not reloading Component when changing url params

I know that it's not a default behaviour / feature of react-router to help us reload easily the current component but I really need this in my application.
My application deals with products. I have a product list that I can load, and when I click on an item, it displays the concerned product details.
On that page, I have related product links that load the same component, but with another product details, located at
<Route path="/products/:id/details" component={ProductDetail} />
I m fetching data in my componentWillMount, and it seems that if I only change the URL, a new component is NOT mounted, and so, I m always having my old data displayed, without fetching anything.
As a beginner using React, I'm looking for some help, or some tricks to reload the component concerned by that page. I mean being able to reload the ProductDetail with the good product.
I tried to look around with componentWillUpdate (a method in which I can see that the router URI changes :D) but I can't setState inside of it to make my component reload (it doesn't seem to be a good practice at all)
Any idea how can I make this work ?
EDIT : According to the first answer, I have to use onEnter. I m now stuck with the way of passing state/props to the concerned component :
const onEnterMethod = () => {
return fetch(URL)
.then(res => res.json())
.then(cmp => {
if (cmp.length === 1) {
// How to pass state / props to the next component ?
}
});
};
The way to handle it depends if you are using flux, redux or however you want to manage your actions. On top of it I would try to make use of onChange property of Route component (check React router docs):
<Route path="/products/:id/details" component={ProductDetail} onChange={someMethod} />
And then in the someMethod create the action if you are using redux or however is done in flux.
The redux would be:
<Route path="/products/:id/details" component={ProductDetail} onEnter={onEnterHandler(store)} />
And the onEnterHandler with the redux store:
function onEnterHandler(store) {
return (nextState, replace) => {
store.dispatch({
type: "CHANGEPRODUCT",
payload: nextState.params.id
})
};
}
And then in your ProductDetail component you would print the new information (It would require a bit more of learning in redux and redux-sagas libraries to complete that part).
Keep in mind that React is just the view part, trying to solve all those problems using only react is not only not recommended but also would mess up your code.

Categories

Resources