How to re-render component after fetch request and state change? - javascript

I'm new to react and tying in the back end but after I make the fetch requests, I have to reload the page to see any changes. The database is updated as soon as the functions are called but the component doesn't re-render. I know setState works asynchronously, so I tried calling my functions in the callback of setState but that did not work.
This happens on both my handleSubmit and handleDelete functions. My initial get request is in my componentDidMount so I'm including that in case it helps.
I couldn't find the answer that I needed on the site, maybe the recommendations were just off but here I am, lol. Thanks in advance.
componentDidMount() {
// todos is the data we get back
// setting the state to newly aquired data
fetch("/api/todos")`enter code here`
.then(res => res.json())
.then(todos => this.setState({ todos }, () =>
console.log("Todos fetched...", todos)))
.catch(err => console.log(err))
}
// onClick for submit button
handleSubmit = (e) => {
e.preventDefault();
const data = this.state;
fetch("/api/todos", {
method: "post",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
};
// onClick for delete button
handleDelete = (e) => {
e.preventDefault();
let uniqueId = e.target.getAttribute("id")
fetch(`/api/todos/${uniqueId}`, {
method: "delete",
headers: { 'Content-Type': 'application/json' }
})
};
// Some of the JSX if needed
<DeleteBtn
id={todo._id}
onClick={this.handleDelete}
>X</DeleteBtn>
<Form onSubmit={this.handleSubmit} id="myForm"></Form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The result I'm looking for is once I add a todo, for it to render on my list immediately, rather than only upon page reload.

Return the details from the back-end in the requests, use that values to update the state,
Currently you just perform the operation on the back-end and front-end doesn't know that it happened in the back-end.
The Best way is to either pass the full data(list or object) back to front-end after operation performed on the DB and link the values to a state,
if the data is bulk then send a success message(200 is enough) back from back-end to front-end and if success change the value(list) in front-end,
Link the value(list) to a state in front-end to have a re rendering of the component.

you've to update your state, and once you'll update the state your component will re-render and it'll shows the latest changes.
Here i am assuming "todos" you've set in your state is an array, then just update it on deleting and adding.
i.e:
// onClick for submit button
handleSubmit = (e) => {
e.preventDefault();
const data = this.state;
const currentTodos = [...this.state.todos]
fetch("/api/todos", {
method: "post",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(()=>{
currentTodos.push(data);
this.setState({todos:currentTodos})
})
};
// similarly for delete you can do
// onClick for delete button
handleDelete = (e) => {
e.preventDefault();
let uniqueId = e.target.getAttribute("id")
let currentTodos = [...this.state.todos];
fetch(`/api/todos/${uniqueId}`, {
method: "delete",
headers: { 'Content-Type': 'application/json' }
}).then(()=>{
let updatedTodos = currentTodos.filter(todo=>todo._id !==uniqueId);
this.setState({todos:updatedTodos})
})
};

You are probably not changing your state "todos" that is why it doesn't render. You could fetch todos after every change (after remove, update, add...) or change the state yourself.
Methode 1:
componentDidMount() {
this.getTodos();
}
getTodos = () => {
//fetch todos, setState
}
handleSubmit = () => {
fetch(...).then(this.getTodos);
}
handleDelete = () => {
fetch(...).then(this.getTodos);
}
Methode 2:
componentDidMount() {
this.getTodos();
}
getTodos = () => {
//fetch todos, setState
}
handleSubmit = () => {
fetch(...);
let todos = this.state.todos;
todos.push(newTodo);
this.setState({todos});
}
handleDelete = () => {
fetch(...);
let todos = this.state.todos;
//remove todo from todos
this.setState({todos});
}

Related

react component constantly fetching and updating data for no reason

I have a react component that I'm fetching data with to visualize.
The data fetching is happening constantly, instead of just once as is needed. I was wondering if there is a way to reduce this happening.
The component is like this,
export default function Analytics() {
const {
sentimentData,
expressionsData,
overall,
handleChange,
startDate,
endDate,
sentimentStatistical,
} = useAnalytics();
return (
I'm wondering if I should be using something like componentDidMount() here with componentDidUpdate() ?
UseAnalytics is another component specifically for fetching data, basically just a series of fetches.
There are different buttons to click on the site that can change the data requested, so I do want to be able to change the state of these data objects / request more, i.e., I filter the data based on dates. But confused how to stop it just constantly requesting data.
Thanks in advance,
Update to share the function being called.
export default function useAnalytics() {
let d = new Date();
d.setMonth(d.getMonth() - 1);
const [dateRange, setDateRange] = useState([d.getTime(), Date.now()]);
const [startDate, endDate] = dateRange;
const { data: sentimentData } = useSWR(
`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`,
fetchSentiment
);
const { data: expressionsData } = useSWR(
`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`,
apiRequest
);
return {
sentimentData,
expressionsData,
overall,
handleChange,
setDateRange,
sentimentStatistical,
startDate,
endDate,
};
}
The apirequest is like this,
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
With the answers,
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
This seems about right, I'm wondering how to substantiate the constants outside of useAnalytics?
In the functional component we don't have componentDidMount() or componentDidUpdate () but we have the workaround for the same by using the useEffect react hook.
For implementing the functionality of componentDidMount() we can utilize the following code snippet
useEffect(()=>{
// this is the callback function that needs to be called only once (when the component has mounted)
},[])
And for implementing the functionality of componentDidUpdate() we can utilize the following code snippet
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
First of all, You are using function component, here you cannot use ComponentDidMount() or ComponentDidUpdate() as they only work in class Components. You will have to use useEffect() but you haven't provided any additional code to understand the situation here.
Anyway if you are setting some state on button click when you fetch data and it is occurring again and again it's probably because you haven't used second argument on useEffect() which states that this useEffect() will only run when the second argument changes.
So to answer your question, pass second argument to useEffect() that you are setting when you click a button.
useEffect(() => {
//
}, [state])
As per your code it's functional component and you can't use componentDidMount() and componentDidUpdate() method as this functions are used in class component.
If you want prevent constantly updating component then there are different ways for both class and functional component.
For Class Component: Only executes if old and new data doesn't match. this function does comparison between old and new value.
componentDidUpdate(prevProps, prevState) { if (prevState.data !== this.state.data) { // Now fetch the new data here. } }
For Functional Component: This is only execute when previous state change.
useEffect(() => { // your data }, [stateName])

Is there a way to put react hooks in a function that's not function component?

I'm trying to create a function that is not a component nor hooks that's callable on speficic event. Let's say i have a simple function that post a data using axios and i want to use navigate after the post is successfull. Here's the example
export const authLogin = (email, password) => {
const config = {
withCredentials: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': Cookies.get('csrftoken')
}
}
let navigate = useNavigate();
return dispatch => {
console.log('Masuk ke dalam auth file');
dispatch(authStart());
axios.post('/log_in/', {
email: email,
password: password
}, config)
.then(res => {
if (res.data.error) {
alert(res.data.error)
dispatch(authFail(res.data.error))
}
else {
const token = res.data.key;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate);
dispatch(authSuccess(token));
dispatch(checkAuthTimeout(3600));
alert('login berhasil')
navigate("/", { replace: true });
}
})
.catch(err => {
alert(err);
dispatch(authFail(err))
})
}
}
I have an error that says
but when i try to change the function name with an uppercase letter, another problem occured, how do i resolve this problem?
A hook must be attached to a fiber which is directly attached to the React component tree. You CANNOT use hooks outside the component tree because React can't keep track of them (this is why hooks must always be run in the same order, and can't be conditional, because React keeps track of their state internally).
The only time you can use a hook outside of a component, is from another hook.
In short, you must be able to draw a straight line back from the hook call to React rendering the component tree. If you cannot, then it's an invalid hook call.
THE SOLUTION
...in your case - is to simply pass in the navigate function to your action as a parameter:
const MyComponent = () => {
const navigate = useNavigate();
const doSomethingHandler = () => {
dispatch(authLogin(email,password,navigate))
}
}
const authLogin = (email,password,navigate) => {
// ...do your action and call the `navigate` parameter
// when you need to
}

Data is not updating with React Hook useEffect()

I have a little web application that uses axios to fetch some orders from the API. My problem is the new orders only appear when the page is refreshed, they do not update automatically.
Here is my useEffect hook:
useEffect(() => {
setLoading(true);
apiClient
.getEvents()
.then((res) => {
console.log(res);
setOrders(res.data);
setLoading(false);
})
.catch((err) => {
console.log(err);
});
}, []);
And here is where I use axios:
import axios from "axios";
const username = "user";
const password = "pass";
const token = Buffer.from(`${username}:${password}`, "utf8").toString("base64");
const apiClient = axios.create({
baseURL: "API URL",
withCredentials: false,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Basic ${token}`,
},
});
export default {
getEvents() {
return apiClient.get("/orders");
},
getEvent(order_id) {
return apiClient.get("/orders/" + order_id);
},
};
Your problem ist the useEffect-Hook itself.
useEffect(() => {
setLoading(true);
apiClient
.getEvents()
.then((res) => {
console.log(res);
setOrders(res.data);
setLoading(false);
})
.catch((err) => {
console.log(err);
});
}, []);
You pass an empty array as the second argument here: }, []);. When you pass an empty array as second argument to useEffect, it will only run on first mount and then never again.
You can pass different variables in this parameter. In this case, useEffect will run, when of these variables change their value. So, for example, you could have a "Refresh" button which changes a state called refresh which you then pass as a second argument to useEffect.
const [refresh, setRefresh] = useState(false);
useEffect(() => {
if(!refresh) return;
setLoading(true);
apiClient
.getEvents()
.then((res) => {
console.log(res);
setOrders(res.data);
setLoading(false);
setRefresh(false);
})
.catch((err) => {
console.log(err);
});
}, [refresh]);
Just a simple example, it could be done better, of course.
Also, one hint: You can omit the second parameter, the dependency array, of useEffect, which would make it run on every update of your component. Don't do this. In most cases, you will end up in an infinite loop because most of the time you will update the state and cause the component to rerender within useEffect - in your case, using setLoading() would be enough.

Use Redux store as input for Post API call

I built an input interface in React and stored the data in a Redux state.
Then, I created a new action for a Post API call. If the "data" parameter is a constant (that I created as a test), everything works fine.
In reality, I'd like to use a key from the Redux state as the data parameter.
In the example, I'm getting an error because props are not defined.
Does it makes sense to connect this action to the state? If yes, how to do it? Thanks a lot!
import axios from 'axios';
export const show_query_action = () => {
return dispatch => {
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: this.props.reducer_components
})
.then(res => {
dispatch(show_query_success_action(res));
})
.catch(err => {
dispatch(show_query_failure_action(err.message));
});
};
};
const show_query_started_action = () => ({
type: 'ADD_SHOW_QUERY_STARTED'
});
const show_query_success_action = todo => ({
type: 'ADD_SHOW_QUERY_SUCCESS',
payload: {
...todo
}
});
const show_query_failure_action = error => ({
type: 'ADD_SHOW_QUERY_FAILURE',
payload: {
error
}
});
If it needs to be callable with different parameters from a React component you can put the data parameter as a parameter to your action creator:
export const show_query_action = (data) => {
return dispatch => {
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: data
})
This is also easily testable. If you only call this action with parameters from the redux store, you can use the second parameter from redux-thunk (wich i presume you are using here):
export const show_query_action = () => {
return (dispatch, getState) => {
const data = getState().data; -> you would specify in wich part of the state your data lives, this is just an example
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: data
})
Hope this helps.

ComponentWillMount renders again after onclick

When you load the page ComponentWillMount triggers getLocalStorage function. This has several checks and triggers the search function. When you load the page it is trying to retrieve the query from localStorage. When you change the input(which changes the query) and submit, the search function should trigger but does not fetch.. instead it refreshes the page and load componentDidMount again? Then after the refresh it works perfectly. Why is it only refreshing one time?
componentWillMount(){
this.getLocalStorage();
};
getLocalStorage = () => {
//Check if localstorage is supported by browser
if (typeof(Storage) !== "undefined") {
//Check if localstorage item query is defined
if (localStorage.getItem("query") !== null) {
//Set query to localstorage item and go to search function
//This works and triggers the search function
this.setState({
query: localStorage.getItem("query")
},() => {
this.search();
});
}
// If localstorage item is not defined go to location
else{
this.getLocation();
}
}
// If localstorage is not supported by the browser go to location
else {
this.getLocation();
}
};
When you click on the button it triggers the search function but does not fetch. Instead it trigger componentDidMount again?
<input type="text" onChange={this.handleChange} placeholder="Find your location..."/>
<button onClick={this.search}>Submit</button>
Search function
search = () => {
this.setState({
secondLoader:true
});
let BASE_URL = "https://maps.googleapis.com/maps/api/geocode/json?";
let ACCES_TOKEN = "token";
let FETCH_URL = `${BASE_URL}address=${this.state.query}&key=${ACCES_TOKEN}`;
alert('the search function does not fetch like below instead it trigger componentDidMount again');
fetch(FETCH_URL, {
method: "GET"
})
.then(response => response.json())
.then(json => {
//If repsonse got zero results use amsterdam location
if(json.status === 'ZERO_RESULTS'){
this.setState({
query: 'Amsterdam'
});
}
//Otherwise use query
else {
const geocode = json.results[0].geometry.location;
this.setState({
latitude: geocode.lat,
longitude: geocode.lng
});
}
const BASE_URL = "https://api.darksky.net/forecast/";
const ACCES_TOKEN = "token";
const FETCH_URL = `${BASE_URL}${ACCES_TOKEN}/${this.state.latitude},${this.state.longitude}?lang=nl&units=si`;
fetch(FETCH_URL, {
method: "GET",
})
.then(response => response.json())
.then(json => {
const data = json;
this.setState({
weather: data,
loader: false,
secondLoader: false
});
})
})
};
Try adding a type attribute to your button. I believe the reason why it's refreshing is that it has a default type of submit. Try using type="button" More info here.

Categories

Resources