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

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.

Related

React component is re-rendering items removed from state

This is a difficult one to explain so I will do my best!
My Goal
I have been learning React and decided to try build a Todo List App from scratch. I wanted to implement a "push notification" system, which when you say mark a todo as complete it will pop up in the bottom left corner saying for example "walk the dog has been updated". Then after a few seconds or so it will be removed from the UI.
Fairly simple Goal, and for the most part I have got it working... BUT... if you quickly mark a few todos as complete they will get removed from the UI and then get re-rendered back in!
I have tried as many different ways of removing items from state as I can think of and even changing where the component is pulled in etc.
This is probably a noobie question, but I am still learning!
Here is a link to a code sandbox, best way I could think of to show where I am at:
Alert Component State/Parent
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/layout/PageContainer.js
Alert Component
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/parts/Alert.js
Any help much appreciated!
When you call a set function to update state, it will update from the last rendered value. If you want it to update from the last set value, you need to pass the update function instead of just the new values.
For instance, you can change your setTodos in your markComplete function to something like this.
setTodos(todos => todos.map((todo) => {
if (id === todo.id) {
todo = {
...todo,
complete: !todo.complete,
};
}
return todo;
}));
https://codesandbox.io/s/jovial-yalow-yd0jz
If asynchronous events are happening, the value in the scope of the executed event handler might be out of date.
When updating lists of values, use the updating method which receives the previous state, for example
setAlerts(previousAlerts => {
const newAlerts = (build new alerts from prev alerts);
return newAlerts;
});
instead of directly using the alerts you got from useState.
In the PageContainer.js, modify this function
const removeAlert = (id) => {
setAlerts(alerts.filter((alert) => alert.id !== id));
};
to this
const removeAlert = (id) => {
setAlerts(prev => prev.filter((alert) => alert.id !== id));
};
This will also fix the issue when unchecking completed todos at high speed

Functional component breaks on browser refresh because object that is pulled from redux state isn‘t available on time

I frequently have the problem, that a functional components breaks on browser refresh because an object that is needed in the component has to be reloaded (i.e. pulled from redux state via mapStateToProps()) and is not available on time.
One solution for this problem seems to be to persist that object in session storage so it can be pulled from there after the browser refresh.
However, this seems like such a terrible workaround.
I am sure there is a better solution to what I believe is a very common problem.
It's because your component will be rendered at least once before getting the value from Redux as you said.
We usually do something like this :
// Be careful on the test if this is an array, test the length etc
if (!yourVariableToTest) return null;
return (
<div>
// Your content ...
</div>
);
I am assuming you are storing an object or similar in your Redux state that is fetched asynchronously. Causing page navigations to work seamlessly (considering the state has been cached previously) and refreshes to fail (because the selector has no state to act upon).
My suggestion is very simple: loading.
Ideally, you should not render the component until all the state is ready.
export function Component() {
const something = useSelector(getSomething)
if (!something) {
return null; // or return <LoadingSpinner />
}
return (
<YourComponent />
)
}
Yes, it is a very common problem, You could use useEffect and useState hook to resolve this problem. If the required object is a type of array then initialized with an empty array :
const [myVariable,setValue] = useState([])
** And if the type of data is different then initialize accordingly **
and update the value when it came from the store
useEffect(()=>{
if(valueFromStore){
setValue(valueFromStore)
}
},[valueFromStore])
try this it won't throw any exception on refresh.
import React,{useState,useEffect} from 'react'

React Hooks - useEffect, call fuctions just when I update an specific property

This is my useEffect:
useEffect(() => {
handleFetchUsers();
handleFetchBooks();
}, [month, listUsers, listBooks]);
I want to fetch books and users the first time (and also when the user change the month), and I also need fetch books and user when there are changes outside the component (when cache changes, I want to fech from cache).
The problem here is that there are 3 properties, and when books is updated, the useEffect function dispatch all fetchs, I do not want fetch users if user has not been updated.
Is there a way to fetch books just when books update and fetch uses just when users update?
Right now, I have all fetch duplicated.
I remember in componentWillUpdated, I solve this problem comparing nextProps and currentProps:
if(nextProps.users !=== props.users){
fetchUsers();
}
I want a performance like that.
You can (and should!) use useEffect more than once in a functional component. Separate your concerns this way, and you should see the behaviour you are looking for.
useEffect(() => {
handleFetchBooks()
},[month])
useEffect(() => {
handleFetchUsers()
},[YOUR_USER_DEPENDENCIES])
On every rerender react checks the dependencies array and refires the hook if any item has changed, so by keeping them separate you can control this behaviour.
useEffect was made keeping this in mind, ideally there should be a single task or lets say side effect attached.
So basically just like #Cal Irvine answer, You can have separate effects for your side effects.
useEffect(() => {
taskrelatedtomonthdependant();
}, [month]);
useEffect(() => {
taskrelatedtouserdependant();
}, [month]);

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

How to change screen and setState at the same time on first click in React Native

I have a function that changes the screen and sets the state at the same time that works given below (initial state of weapon is null):
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
navigate("Second", {weapon: this.state.weapon});
}
And I am calling this on my second screen via {this.props.navigation.state.weapon}, but the state doesn't seem to update to M16 until I go back and click the button again.
I have console logged both above and below the setState function and on the first click it always gives me null but M16 when I go back and click it again.
Can I not run setState at the same time as navigating between screens? If I can what am I doing wrong.
TLDR: I'm trying to set state and change page in same function so I can then display the new state on the new page as text. The state change doesn't happen until the second click of the button.
Try putting a small timeout for the navigate. The state change may not be complete when you hit the navigate instruction
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
setTimeout(() => navigate("Second", {weapon: this.state.weapon}),20);
}
State is supposed to be used as a helper to handle a small amount of data inside your component. The state life cycle ends as the component it belongs completely unmount. Also, note that setState is an asynchronous function, so you must not rely on React to handle sync situations for you. Updating your state will also make your component rerender, so you should use it carefully to avoid loss memory unnecessarily.
If you just want to pass data from a component to another, in this case using navigation props is enough, like this navigate("Second", {weapon: 'M16'});. You don't need to update your state to then be able to pass this data further. In fact, it makes no sense to update your state before navigation, since the current state itself will be lost in the next screen.
If you need to share the exact same state prop between more components, which doesn't seem to be the case, maybe you should consider using another approach, like Redux (https://redux.js.org/).
I recommend you to read the official docs for more detailed info:
https://reactjs.org/docs/react-component.html#state
https://reactjs.org/docs/state-and-lifecycle.html
Hope it helps
Edit:
Based on the information you provided below, if weapon will be an array, for example, and you need to push a new value to it before navigation, you should not use setState, try this instead:
const { navigate } = this.props.navigation;
clickM16 = () => {
const { weapon } = this.state;
weapon.push('M16');
navigate("Second", { weapon });
}
Hope it helps
I will give another suggestion:
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
let sendPara = this.state.weapon
navigate("Second", {weapon: sendPara});
}
Recive parameter in respective component.
catName={this.props.navigation.state.params.sendPara}
I hope this may help you.

Categories

Resources