How can I call function even before useState? - reactjs

I have a context state that contains that is on page UserState.js
data: null
columns: null
And I am calling it via useContext on UserTable.js
const userContext = useContext(UserContext);
const {data,columns} = userContext;
And for what is inside the data and columns, it will be extracted by calling API via axios.
I am facing difficulty is that, for the particular page, I also have a localData and localColumns that is
const [localColumns, setlocalColumns] = useState([...columns]);
const [localData, setlocalData] = useState([...data]);
Where because when calling it, the data is still null and hence it returns error.
I have tried extraction from the database using useEffect and/or useLayout
The overall file structure is like this
-src
--components
---context
----user
-----UserState.js
--pages
---UserPage
----UserTable.js
And the UserTable.js looks like this
const UserTable = () => {
useLayoutEffect(() => {
{
getAllUser();
}
});
const { data, columns,setAllUserFunc } = userContext;
const getAllUser = () => {
axios
.get("http://localhost:3000/v1/users", {
headers: {
Authorization: "Bearer " + token.access.token,
},
})
.then((response) => {
let allUser = response.data.results;
let allNormalUser = allUser.filter((user) => user.role == "user");
setAllUserFunc(allNormalUser);
})
.catch((error) => {});
};
const [localColumns, setlocalColumns] = useState([...columns]);
const [localData, setlocalData] = useState([...data]);
return (
<Table columns={columns} dataSource={data} />
)
}
The setAllUserFunc will set the data and columns to be their corresponding that is on the UserState.js and I confirm it is working (by commenting on the localData and localColumns as well as table).
My question is how can I call the function before the useState that set localColumns?
useEffect and useLayoutEffect don't work

Related

how to cast/parse Axios REST response field from string to Date within React?

What would be the easiest way to effectively "convert" (parse/cast) the Date strings from a REST API response to true Dates? So that after "setItems" is called the contents of "items" is correctly typed as per the "ItemsType". Is there an easy way to do this in TypeScript/javascript?
Code below. Currently when I call setItems everything remains as strings, even though I have called out the items variable should be of type "ItemsType[]", so I guess it's not as if typing the variable magically makes it happen, and in fact there is no error thrown.
type ItemsType = {
id: number
locationName: string
checkinDate: Date
checkoutDate: Date // edited-this shoudl have been Date also
}
function App() {
const [items, setItems] = useState<ItemsType[]>([])
useEffect(() => {
(async () => {
try {
const res = await axios.get(`https://localhost:7040/Tripstop`)
const rawItems = await res.data
setItems(typedItems)
}
)()
}, [])
Sample incoming REST data:
You could always implement a generic Axios response transformer to decode anything that looks like a date.
For example, this creates an Axios instance that transforms any value with the word "date" in the key
const dateKeyRx = /date/i;
export const api = axios.create({
baseURL: "http://localhost:7040/",
transformResponse: (data) =>
JSON.parse(data, (key, value) =>
dateKeyRx.test(key) ? new Date(value) : value
),
});
But I'd be more inclined to create a service for retrieving and transforming specific responses. This would encapsulate your logic without exposing the specific implementation to any consumers.
For example
// svc/tripstop.ts
import axios from "axios";
interface ResponseItem {
id: number;
locationName: string;
checkinDate: string;
checkoutDate: string;
}
export interface ItemsType extends ResponseItem {
checkinDate: Date;
checkoutDate: Date;
}
export const getTripstop = async (): Promise<ItemsType[]> => {
const { data } = await axios.get<ResponseItem[]>(
"https://localhost:7040/Tripstop"
);
return data.map(({ checkinDate, checkoutDate, ...rest }) => ({
...rest,
checkinDate: new Date(checkinDate),
checkoutDate: new Date(checkoutDate),
}));
};
Then you can use it in your React components
import { getTripstop } from "./svc/tripstop";
// ... snip
useEffect(() => {
getTripstop().then(setItems).catch(console.error);
}, []);
You could still use the response transformer which would save you the extra .map() call
export const getTripstop = async () =>
(await api.get<ItemsType[]>("/Tripstop")).data;
You can cast something like this
const App = () => {
const [items, setItems] = useState<Array<ItemsType>>([]);
const getData = async () => {
const res = await axios.get(`https://localhost:7040/Tripstop`)
const rawItems = await res.data as Array<ItemsType>;
const parsedData = rawItems.map((x) => ({
...x,
checkInDate: new Date(x.checkInDate).toDateString(),
checkOutDate: new Date(x.checkOutDate).toDateString()
}))
setItems(parsedData)
}
useEffect(() => getData(), [])
}

React rendering properly but not reading correct state in function

useEffect(() => {
getNotes()
}, []);
const getNotes = async () => {
const notes = await fetch(`${getNotesPath()}`);
const notesData = await notes.json();
setEstimateNotes([...notesData]);
};
const createNote = async () => {
console.log(estimateNotes); // gives empty string
let noteParams = {
... // this all works fine and note is created in db
};
const url = `${getNotesPath()}/create`;
const body = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
...noteParams,
}),
};
const newNote = await fetch(url, body);
const newNoteData = await newNote.json();
setEstimateNotes([...estimateNotes, newNoteData]) // renders 1 note
};
const destroyNote = async (id) => {. // works great!
const destroyedNote = await fetch(`/api/notes/${id}/destroy`);
const destroyedNoteData = await destroyedNote.json();
setEstimateNotes(
[...estimateNotes].filter((note) => note.id !== destroyedNoteData.id)
);
};
return estimateNotes.map((note, i) => {
return (
<div key={`notes-${i}`} className="note">
... renders all my notes fine
</div>
);
});
I'm having a very strange problem with React. For some reason my state seems to be working fine and everything renders properly but when I go to update it in my createNote function it's like it's completely disconnected. I console.log an empty array while I'm rendering all my notes from the same state. If I manipulate the state it just starts from the empty array instead of what I see. What is even stranger is that my destroy function works just fine.
I've never seen this before and can't find anything else about it on Stack Overflow. Usually the problem is going the other way (not rendering on state update)

useEffect doesn't run after rendering

I'm kind of confused about how useEffect is triggered and how it work. I wrote a function like this but the useEffect doesn't run at all. I want to fetch the data from the API and then render a page based on the data. But it doesn't trigger the useEffect. If I don't use the useEffect, it will render the page three times.
async function getData() {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
tmpArrData = data;
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
function App() {
const [arrData, setArrData] = useState();
const [loadData, setLoadData] = useState(false);
useEffect(() => {
console.log("if it works, this line should be shown");
const tmpArrData = getData();
setArrData(tmpArrData);
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
The useEffect hook is guaranteed to run at least once at the end of the initial render.
getData is an async function and the useEffect callback code is not waiting for it to resolve. Easy solution is to chain from the implicitly returned Promise from getData and access the resolved value to update the arrData state. Make sure to remove the state from the useEffect's dependency array so that you don't create a render loop.
The getData implementation could be clean/tightened up by just returning the fetch result, no need to save into a temp variable first.
async function getData() {
return await fetch(".....")
.then((res) => res.json());
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData().then((data) => {
setArrData(data);
});
}, []); // <-- empty dependency so effect called once on mount
Additionally, since arrData is initially undefined, arrData[0] is likely to throw an error. You may want to provide valid initial state, and a fallback value in case the first element is undefined, so you don't attempt to access properties of an undefined object.
const [arrData, setArrData] = useState([]);
...
const data = arrData[0] || {}; // data is at least an object
return (
<GifCompoment
id={data.id}
name={data.name}
activeTimeTo={data.activeTimeTo}
activeTimeFrom={data.activeTimeFrom}
requiredPoints={data.requiredPoints}
imageUrl={data.imageUrl}
/>
);
You should call state setter insede of Promise
function App() {
const [arrData, setArrData] = useState();
function getData() {
fetch("/api/hidden")
.then((res) => res.json())
.then((data) => setArrData(data));
}
useEffect(() => {
console.log("if it works, this line should be shown");
getData();
}, []);
return ...
}
By combining the answer from Drew Reese and Artyom Vancyan, I have solved my problem. I think the key points are setState right in the then function .then((data) => setArrData(data)) ,don't put the dependency in the useEffect, and await inside the useEffect. Thank you guy super ultra very much. Big love
useEffect(() => {
console.log("if it works, this line should be shown");
const getData = async () => {
await fetch("hidden API")
.then((ref) => ref.json())
.then((data) => {
setArrData(data);
});
}
getData();
}, []);
function App() {
const [arrData, setArrData] = useState([]);
const [loadData, setLoadData] = useState(false);
const async getData=()=> {
var tmpArrData = [];
await fetch("this API is hidden due to the privacy of the company - sorry")
.then((res) => res.json())
.then((data) => {
console.log("data", data);
setArrData(tmpArrData);
});
console.log("tmpData ", tmpArrData);
return tmpArrData;
}
useEffect(() => {
console.log("if it works, this line should be shown");
const callApi =async()=>{
await getData();
}
}, [arrData]);
const data = arrData[0];
console.log(data);
return (
<GifCompoment
id = {data.id}
name = {data.name}
activeTimeTo = {data.activeTimeTo}
activeTimeFrom = {data.activeTimeFrom}
requiredPoints = {data.requiredPoints}
imageUrl = {data.imageUrl}
/>
);
}
export default App;
Page will be rendered three to four times it's normal.

ts/react - fetch in useEffect gets called multiple times

in my functional component I want to fetch data once the component mounts. But unfortunately, the request gets fired three times, until it stops. Can you tell me why?
const [rows, setRows] = useState<any[]>([]);
const [tableReady, setTableReady] = useState<boolean>(false);
const [data, setData] = useState<any[]>([]);
const getData = async () => {
const user = await Amplify.Auth.currentAuthenticatedUser();
const token = user.signInUserSession.idToken.jwtToken;
const apiurl = 'xxx';
fetch(apiurl, {
method: 'GET',
headers: {
'Authorization': token
}
})
.then(res => res.json())
.then((result) => {
setData(result);
})
.catch(console.log)
}
useEffect(() => {
if (!tableReady) {
getData();
if (data.length > 0) {
data.forEach((element, i) => {
const convertedId: number = +element.id;
setRows(rows => [...rows, (createData(convertedId, element.user))]);
});
setTableReady(true);
}
}
}, []);
return (
<div className={classes.root}>
<MUIDataTable
title={""}
data={rows}
columns={columns}
/>
</div>
);
I updated my question due to the comment.
The useEffect is missing a dependency array, so its callback is invoked every time the component renders.
Solution
Add a dependency array.
useEffect(() => {
if (!tableReady) {
getData();
if (data.length > 0) {
data.forEach((element, i) => {
const convertedId: number = +element.id;
rows.push(convertedId);
});
setTableReady(true);
}
}
}, []); // <-- dependency array
An empty dependency array will run the effect once when the component mounts. If you want it to ran when any specific value(s) update then add these to the dependency array.
See Conditionally firing an effect
Edit
It doesn't appear there is any need to store a data state since it's used to populate the rows state. Since React state updates are asynchronously processed, and useEffect callbacks are 100% synchronous, when you call getData and don't wait for the data to populate, then the rest of the effect callback is using the initially empty data array.
I suggest returning the fetch request from getData and just process the response data directly into your rows state.
const getData = async () => {
const user = await Amplify.Auth.currentAuthenticatedUser();
const token = user.signInUserSession.idToken.jwtToken;
const apiurl = 'xxx';
return fetch(apiurl, {
method: 'GET',
headers: {
'Authorization': token
}
});
}
useEffect(() => {
if (!tableReady) {
getData()
.then(res => res.json())
.then(data => {
if (data.length) {
setRows(data.map(element => createData(+element.id, element.user)))
}
})
.catch(console.error)
.finally(() => setTableReady(true));
}
}, []);

React - fetch data and store in a variable

Beginner with react and struggling with something that I am sure is probably very simple. I am just trying to make a simple component that will fetch data and display a part of it in a div. I am able to get the data and print it to console, but I am having trouble saving to a variable and displaying it. Here is my code (removed the actual url for privacy reasons):
let x = -1;
function getData(apiUrl){
fetch(apiUrl, {credentials: 'same-origin'})
.then((response) => {
if (!response.ok) {
Logging.error(`Did not get an ok. got: ${response.statusText}`);
}
return response.json();
})
.then(json => {x = json.value})
.catch((error) => {
Logging.error(`Error getting ad data: ${error.message}`);
})
}
const MyPage = () => {
getData('my endpoint')
return (
<div>{x}</div>
);
}
My issue is when I load the page it always displays my default value of "-1". So either x is never getting re-assigned, or the return is happening before it does.
Other commenters about setting state is not wrong.
However, you are also not exactly wrong, expecting a value for x.
Your getData function calls fetch, however you did not return anything from fetch. If you want to use x = getData(), you will need to ensure to add a return before the fetch function in order to return the data.
const getData = (apiUrl) => {
return fetch(apiUrl, {credentials: 'same-origin'})
.then((response) => {
if (!response.ok) {
Logging.error(`Did not get an ok. got: ${response.statusText}`);
}
return response.json();
})
.then(json => {x = json.value})
.catch((error) => {
Logging.error(`Error getting ad data: ${error.message}`);
})
}
let x = await getData(apiUrl)
However, fetch is asynchronous so it's you need to use x = await getData().
You cannot use await outside an async function, so you need to use effect, and useState to properly render the data you want.
import React, { useEffect, useState } from 'react';
const MyPage = () => {
const [ data, setData ] = useState();
useEffect(() => {
getData(apiUrl);
},[])
const getData = async (apiUrl) => {
fetch(apiUrl, {credentials: 'same-origin'})
.then((response) => {
if (!response.ok) {
Logging.error(`Did not get an ok. got: ${response.statusText}`);
}
return response.json();
})
.then(json => setData(json)) //setData here
.catch((error) => {
Logging.error(`Error getting ad data: ${error.message}`);
})
}
return (<pre>{ JSON.stringify(data, null, 4)}</pre>)
}
You need to use JSON.stringify to show your JSON results in your return statement.
You need to you use the state in react. Try something like:
import react, { useState, useEfect } from 'react';
const MyPage = () => {
const [data, setData] = useState(null);
const useEfect(() => {
const result = getData('my endpoint');
setData(result);
}, []);
return (
<div>{data}</div>
);
}

Resources