Page renders faster than fetching the data and displaying it - reactjs

How do i display data that i am grabbing using a fetch call and then setting it into an array and displaying in the Dom if it meets the conditions. Currently i am rendering the page faster than i am fetching the data and doing stuff with it.
thanks in advance
function Profile() {
let myReceipe = [];
const [isPopUp, setPopUp] = useState(false);
ReceipeService.getReceipes().then(data => {
myReceipe = data;
})
const popUpHandler = () => {
setPopUp(!isPopUp);
}
return (
<>
<div className='profile'>
<ProfileHeader/>
<div className='createdposts'>
{myReceipe.length !== 0 ?
<Post popUpHandler={popUpHandler} myReceipe={myReceipe}/>
:
null
}
</div>
</div>
{isPopUp === true ?
<Magnify popUpHandler={popUpHandler}/>
:
null
}
</>
)
}

Couple of problems in your code:
You are not using the useEffect hook to make the HTTP request
myReceipe should be in the state of your component
Data will always be loaded after your component has rendered.
The way you are fetching the data is not the correct way to do it. React has useEffect hook that is built exactly for this purpose.
Fetching data from the server is a side-effect and all the side effects belong inside the useEffect hook. So, move the code that makes the HTTP request inside the useEffect hook.
Also make sure that myReceipe is the local state of your component
const [myReceipe, setMyReceipe] = useState([]);
and when the data from the server is available, update the state to trigger a re-render so that you can show the data to the user.
useEffect(() => {
ReceipeService.getReceipes()
.then(data => {
setMyReceipe(data);
});
}, []);
While the data is not available, show some kind of loading spinner to the user to indicate to the user that data is loading.

just use a state variable myReceipe then when myReceipe is set the Component will re render nthen call ReceipeService.getReceipes() in useEffect :
let myReceipe = [];
const [isPopUp, setPopUp] = useState(false);
const [myReceipe , setmyReceipe ] = useState([]);
useEffect(
()=>{
let isMounted=true;
ReceipeService.getReceipes().then(data => {
// isMounted && insures the component is still mounted
// or else this might through an error if the component has unmounted but the api call responded because you cant just update staet of un unmounted Component
isMounted && setmyReceipe(data);
})
return ()=>{isMounted = false }
},[])
const popUpHandler = () => {
setPopUp(!isPopUp);
}
return (
<>
<div className='profile'>
<ProfileHeader/>
<div className='createdposts'>
{myReceipe.length !== 0 ?
<Post popUpHandler={popUpHandler} myReceipe={myReceipe}/>
:
null
}
</div>
</div>
{isPopUp === true ?
<Magnify popUpHandler={popUpHandler}/>
:
null
}
</>
)
}

Related

react-router-dom state returns error on page refresh

I am new to react-router-dom, I was calling Data inside
of the ParentPage.jsx and mapped it using Card.jsx and it returned cards of data. In the
Card.jsx I passed the data to the ChildPage page using <Link/> and it worked, but if I'm going to refresh the child page it returns an error TypeError: Cannot read properties of undefined. I have also tried storing the data on the localStorage but it is still returning the same error. I hope someone can help me.
Here are my code snippets.
ParentPage.jsx
const [establishment, setEstablishment] = useState([]);
const Data = () => {
...
};
const cards = useMemo(() => {
return establishment.map((establishment) => (
<Card establishment={establishment} />
));
});
...
{cards}
...
Card.jsx
const [details] = useState(establishment);
return (
<>
<text>{details.name}</text>
<Link
to={{
pathname: "/establishments/details",
state: { details },
}}
>
<Button>
Details
</Button>
</Link>
</>
);
ChildPage.jsx
const {state} = useLocation();
const [data, setData] = useState(state?.details);
useEffect(() => {
window.localStorage.setItem(data.id, JSON.stringify(data));
}, [data]);
useEffect(() => {
const updatedData = window.localStorage.getItem(data.id);
if (updatedData !== null) setData(JSON.parse(updatedData));
}, []);
...
...data.color
...
Here is the Error
Issue
You are on the right track, but the logic is a little mixed up. Route state is very transient, it only exists during the route transition and while the receiving component remains mounted. Reloading the page reloads the entire React app. Any state in memory is lost.
Current code:
const location = useLocation();
const [data, setData] = useState(location.state?.details); // (A)
useEffect(() => {
window.localStorage.setItem(data.id, JSON.stringify(data)); // (B)
}, [data]);
useEffect(() => {
const updatedData = window.localStorage.getItem(data.id);
if (updatedData !== null) setData(JSON.parse(updatedData));
}, []);
...
.... data.color // (C)
...
Here's what I see occurring:
Navigate to child page with defined state, the data state is initialized to location.state.details (A), and the component renders with defined data.color (C). No error.
The first useEffect hook runs and persists the data state to local storage under the defined key data.id (B).
The second useEffect hook runs and reads from localStorage using defined data.id key and since it's not null enqueues a data state update.
Reload the page.
The app remounts. This ChildPage component remounts. The data state is initialized to the undefined location.state value (A). Error thrown accessing data.color on the initial render (C).
Solution
The data state should be initialized from location.state.data if it exists, then fallback to localStorage if it exists, then possibly fallback to a base value. Use only a single useEffect hook to persist the local state to localStorage when it updates. Use a storage key that is always defined.
const { state } = useLocation();
const [data, setData] = useState(() => {
return state?.details || JSON.parse(window.localStorage.getItem("details")) || {};
});
useEffect(() => {
window.localStorage.setItem("details", JSON.stringify(data));
}, [data]);
...
.... data.color
...
I think the problem is with the parsing of data not the link it self. It would have helped a lot if you had showed some code and the full error message
For this kind of usage, the best practice would be using query param in react-router-dom, so that you can pass your value and by refreshing the page will work the same, you can check if there is no query param in the child component you can redirect the user back
I think this blog will help to handle it https://denislistiadi.medium.com/react-router-v6-fundamental-url-parameter-query-strings-customizing-link-57b75f7d63dd

How to not render anything in react, unless data is loaded and state is completely set

Let's consider this code:
const CustomersList = () => {
const [loading, setLoading] = useState(true);
const [customers, setCustomers] = useState([]);
useEffect(() => {
// fetch customers from an API and then
// setLoading(false)
// setCustomers(customersArrayFromApi)
}, [])
return loading
?
<div>Loading ...</div>
:
<div>
{customers.length === 0
?
<span>No customer</span>
:
<div>List of customers rendered here</div>
}
</div>
}
When I setLoading(false) and setCustomers(customersArrayFromApi), there is this tiny timeframe (even for a millisecond or less than that) that the <span>No customer</span> is rendered.
And we know it's because react's state setting function is async.
But I can't render JSX in useEffect to wait for customers array to be set fully.
What can I do to wait for the customers list to be fully set, and then render the output?
Change the order of setState in useEffect, set the loading false after updating the customers.
And we know it's because react's state setting function is async.
React may batch the setState calls and the state updates may happen asynchronously but batching won't work inside async operations like fetch until React 17.
useEffect(() => {
// fetch customers from an API and then
setCustomers(customersArrayFromApi); //Re-render with customers data
setLoading(false); // Re-render with loading false
}, []);

Can't Update state after fetch request

I'm trying to make an app where I fetch data from a Graphql API, and update state after fetching, but after the successful request I'm getting the data but I can't update state with that data. when I console log the response data it's showing the array with required data but when I update state and console log the state it's showing empty array.
I need to update the state to use it in the component, when i'm doing this it's throwing error that currList is undefined.
here are pictures of code and console.
export default function App() {
const [search, setSeach] = useState("");
const [currList, setCurrList] = useState([]);
const fetchShips = async () => {
const response = await request(gql`
{
ships {
name
home_port
image
}
}
`);
console.log("request response", response.data);
setCurrList(response.data);
console.log("currlist:", currList);
};
useEffect(() => {
fetchShips();
}, [currList]);
const Searchitems = (event) => {
setSeach(event.target.value);
setCurrList(
currList.filter((item) => {
return item.name.includes(search) || item.home_port.includes(search);
})
);
};
return (
<div className="App">
<div className="container">
<header></header>
<div className="body">
<input
type="text"
id="search"
value={search}
onChange={Searchitems}
className="input"
placeholder="Search Ships"
/>
<p>Total Count: {currList.length}</p>
<div className="ships">
{currList.map((item, index) => {
return (
<Ship
key={index}
name={item.name}
port={item.port}
img={item.image}
/>
);
})}
</div>
</div>
</div>
</div>
);
}
the State is Updated just not when the code is running that's why it logs that the state is an empty array, try to console.log it once again and you will see that there is something in the List.
That's normal, everything is happening asynchronously. React will trigger a re-render once your currList is updated and you will get its new value. If you want to listen to this change, you have to use useEffect with currList as a dependency.
useEffect(() => {
console.log("List is updated", currList);
}, [currList]);
The function fetchShips has closure over the currList state variable that blongs to specific render call, so when you log it after calling setCurrList it shows you the previous state not the updated state, you can log it in the useEffect to see the changes.
I had the same problem once. I couldn't find efficient solution but my solution saved me anyway.
I used useRef instead of useState.
Try this:
const currList = useRef([]);
useEffect=(()=>{
const fetchShips = async () => {
const response = await request(gql`
{
ships {
name
home_port
image
}
}
`);
console.log("request response", response.data);
// setCurrList(response.data);
if(response)
currlist.current = response.data
console.log("currlist:", currList);
};
fetchShips()
// Also as far as I know ,you should use useEffect like this
},[])
//... codes
return(
//... codes
currList.current.map(...
)
//... codes
Before using useRef, try to define your fetchShips function inside useEffect so maybe you don't need my solution.
Why is not efficient my solution for your case?
When you want to update your currList data, useRef does not trigger re-render. Even if your data updated, you cannot see it on your screen.
So setCurrList(currList.current) can save you but as I said earlier it may not efficient way.

Problem with dynamically updating a React Component within a chat project

In my chat application, I am having trouble displaying user messages after send button is pressed.
I receive all the message data from an API and store it inside a state. Here is the relevant part.
I simplified the code because I think the problem is in the logic of my code and there are no syntax errors.
const Chat = (props) => {
const [selectedRoom, setSelectedRoom] = useState({})
const [roomMessages, setRoomMessages] = useState([])
let roomMessagesData = []
function getUserRoomMessages() {
fetch("url").then((response) => {
return response.json();
}).then((requestData) => {
setRoomMessages(requestData.message_list)
});
};
function sendMessage(message) {
fetch("url").then((response) => {
return response.json();
}).then((requestData) => {
getUserRoomMessages()
});
}
function handleSendMessageButton() {
let currentMsg = document.querySelector("#textBox").value;
if (currentMsg != "") {
sendMessage(currentMsg)
roomMessagesData.push(
<div className="message receiver">{currentMsg}</div>
)
}
document.querySelector("#textBox").value = "";
}
useEffect(() => {
getUserRoomMessages()
}, [selectedRoom])
// Note that:
// typeof roomMessages !== 'undefined' will always be true,
// since initially, roomMessages = [], it is defined.
// Instead just check if roomMessages is not empty, to avoid this operation.
if (roomMessages.length > 0) {
const sortedMessages = roomMessages.sort(
(a,b)=> a.message_id > b.message_id ? 1 : -1
);
for (let i = 0; i<sortedMessages.length; i++) {
if (sortedMessages[i].creator_id == user_id) {
roomMessagesData.push(
<div className="message receiver">
{sortedMessages[i].message_text}
</div>
);
} else {
roomMessagesData.push(
<div className="message sender">
{sortedMessages[i].message_text}
</div>
);
}
};
}
return (
<div>
//some code
<div className="chatBox">{roomMessagesData}</div>
<div><input type="text" id="textBox"/></div>
<div>
<input
id="btn"
type="button"
value="Send message"
onClick={handleSendMessageButton}
/>
</div>
</div>
)
}
In this current logic, when I press send message button, the message is sent to the API successfully, but not displayed. Only after I refresh the page new message appear.
All of the calls to API are successful.
My idea is that when a user sends a new message I retrieve a newly updated list of messages from API which then should be re-rendered by reactjs. Or I want push a new element to roomMessagesData so that after it changes reactjs should reload its elements.
The question is what is the problem in my code and what is it that I have do to achieve what I described above.
Quick review of the steps:
So handleSendMessageButton triggers the sendMessage function which does a POST.
And then sendMessage triggers a GET message using getUserRoomMessages.
Within getUserRoomMessages, you trigger state update using setRoomMessages.
And the rendered child {roomMessagesData} is dependant on the changes within roomMessages state.
Based on the above, the issue to be fixed is here:
// ONLY re-renders if selectedRoom changes
useEffect(() => { getUserRoomMessages() }, [selectedRoom]);
Change the above to:
// re-render if roomMessages changes,
// and the message should display automatically
useEffect(() => { getUserRoomMessages() }, [roomMessages]);
Remember, that useEffect() will only run again if certain "dependant" states change.
UPDATE
Despite suggesting the above, notice that it will create infinite loop because - getUserRoomMessages makes a API call and then updates roomMessages state which the useEffect depends on to re-render the component - thus leading to neverending re-rendering and numerous API calls.
Suggestion:
Since getUserRoomMessages is already called when the onClick is triggered, thus updating date... you can instead utilize the useEffect to run only when state is updated e.g.
let loaded = React.useRef(false);
// Cleanup useEffect
useEffect(() => {
// set to true on mount...
isLoaded.current = true;
// ... and to false on unmount
return () => { isLoaded.current = false; };
}, [isLoaded, roomMessages]);
Right now you have defined your list using a variable let roomMessagesData = [],whereas you need to use a state to define it. later on when you change that state , your component will be updated since a state has changed.
another way to go is by deploying a useEffect hook in which you will pass roomMessagesData as the second dependancy. This way , when that array changes, your component will rerender.
useEffect(() => {
const copyRoomMessagesData = [...roomMessagesData]
}, [roomMessagesData])
<div className="chatBox">
{copyRoomMessagesData}
</div>

Fetching w/ custom React hook based on router parameters

I'm trying to fetch data with a custom React hook, based on the current router parameters.
https://stackblitz.com/edit/react-fetch-router
What it should do:
On first load, check if URL contains an ID...
If it does, fetch a todo with that ID
If it does not, fetch a todo with a random ID & add ID to url
On fetch button clicks...
Fetch a todo with a random ID & add ID to url
What is wrong:
Watching the console or inspector network tab, you can see that it's firing several fetch requests on each click - why is this and how should this be done correctly?
Since you used history.push on handleClick, you will see multiple requests being sent as you are using history.push on click handler which will cause a re-render and make use use random generated url as well as also trigger the fetchTodo function.
Now a re-render will occur which is cause a randomised id to be generated. and passed onto useTodo hook which will lead to the fetchTodo function being called again.
The correct solution for you is to set the random todo id param on handleClick and also avoid unnecessary url updates
const Todos = () => {
const history = useHistory();
const { id: todoId } = useParams();
const { fetchTodo, todo, isFetching, error } = useTodo(todoId);
const isInitialRender = useRef(true);
const handleClick = () => {
const todoId = randomMax(100);
history.push(`/${todoId}`);
};
useEffect(() => {
history.push(`/${todoId}`);
}, []);
useEffect(() => {
if(!isInitialRender.current) {
fetchTodo();
} else {
isInitialRender.current = false
}
}, [todoId])
return (
<>
<button onClick={handleClick} style={{marginBottom: "10px"}}>Fetch a todo</button>
{isFetching ? (
<p>Fetching...</p>
) : (
<Todo todo={todo} color={todo.color} isFetching={isFetching} />
)
}
</>
);
};
export default Todos;
Working demo

Resources