I am new to React, and I'm currently learning about useState and useEffect. I have successfully fetched data from my local api using axios. For development purposes, I'm intentionally changing the data directly from my database, to see if data changes would reflect in my React app. But, whenever I do so, my app breaks. I have included a screenshot of the error.
Here is my code:
function App() {
const[applicant, setApplicant] = useState("");
useEffect(() => {
axios.get("http://localhost:5000/application/1")
.then((res) => {
setApplicant(res.data);
console.log(res.data.personalInfo.firstName);
})
.catch((err) => console.log(err));
}, []);
return (
<div><h1>Welcome {applicant.personalInfo.firstName}</h1></div>
);
}
So here's the weird part (at least for me). On first load, everything works perfectly fine. But when I change first_name field directly in my database, my app breaks. Here's the screenshot:
Obervation 1: if I change the array dependency of my useEffect to always look at changes in applicant state, the error will not occur. But as you might have already guessed, it causes an infinite loop request.
Observation 2: after my app breaks due to changes in database, if I temporarily remove that part in my JSX where I rendered the fetched data in my component: <h1>Welcome {applicant.personalInfo.firstName}</h1>, refresh the page, then paste it back again, and refresh again, the error disappears, and the newly updated first_name renders successfully.
Can someone tell me what's going on here, and mybe a better way of implementing this? Thanks a lot!
Edit: This is a screenshot of my json data:
So I found the problem. Basically, the type of my state is string, but in my setApplicant(), I'm setting its value to the returned json object. So I just changed the default state type to be an empty object, like so:
const[applicant, setApplicant] = useState({});
Then, in my useEffect hook:
setApplicant(res.data.personalInfo); //setting it to personalInfo object
Then, to access its attributes:
<h1>Welcome {applicant.firstName}</h1>
Related
the following question about the usage of next.js and react to achieve SSR build on top of each other, so I thought I'd write that into a single post. My main question is the third one, but I feel I need to first understand the first two questions in order to get there. So here we go:
1. Am I right that the whole page is always reexecuted from scratch after the client has received it?
Consider this next.js page component:
const Page = () => {
const [state, setState] = useState(getState());
function getState() {
console.log("compute initial state");
return 1;
}
return <>{state}</>;
};
As far as I can tell, getState() is executed both on the server and on the client. If I'd want to exectue that computation only on the server I'd have to do it via getInitialProps()resp. getServersideProps(), right?
2. What value does the prerendered document have for the client if it will be immediately thrown away?
Taking the first question a step further, why is the prerendered document event handed to the client if it will be recalculated from scratch anyway. What advantages does it have for the client to get that initial document? Ok if the case the client can't execute js at all they at least have something. But is that all?
3. Does this mean I unnecessarily have to "double render" some components on the client side?
Let's say parts of my code depend on window and can't be executed on the server. As explained in different articles I found, it can lead to problems (and react warnings) if I rely on checks like typeof window === "undefined" in my code. Instead I think the better way is to execute those functionalities after the first render with a useEffect:
const Page = () => {
const [value, setValue] = useState();
// Effect will be executed after the first render, e.g. never on the server
useEffect(() => {
const value = window.innerWidth; // Some computations or subscriptions that depend on window
setValue(value)
}, []);
return (
<>
{!value && <h1>value pending ...</h1>}
{value && <h1>{value}</h1>}
</>
);
};
Now, with this pattern the app is fine from an SSR perspective. BUT I am introducing additional effort that has do be done on the client side: Even though window is defined on first render, it can only be used on second render.
For this smaller example this doesn't play a big role, but in a larger app, a flickering may be visible because an unnecessary first render is updated some milliseconds later. Furthermore the code becomes harder to read and more error-prone.
The solution I would want here is related to the first question above: Can I somehow avoid the app from starting from scratch but directly start with the second render? Is there some kind of pattern for this that I've missed so far?
Note: Of course I could rewrite the component to simply check if window is defined:
return (
<>
<h1>{window ? window.innerWidth : "value pending ..."}</h1>
</>
);
However this will cause a react warning and cause other problems as described in the article i have liked above. Also see the react docs:
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them.
Thank you a lot for your help!
Am I right that the whole page is always reexecuted from scratch after the client has received it?
Yes and no. The initial html is built on the server and sent to the client as html, and then react is hydrated so that your page becomes interactive. As an example, consider this "page":
export default function MyPage() {
const [greeting, setGreeting] = React.useState("Hello")
React.useEffect(() => {
setGreeting("Goodbye")
}, [])
return (<p>{greeting}</p>)
}
When the page is rendered on the server, it will render as html and send that html to the client. So if you inspect the source code of the page, you'll see:
<p>Hello</p>
Then, on the client, react is hydrated and useEffect runs, so in the browser you'd see a paragraph with the word "Goodbye".
This is true whether you're using SSR (the html is created on demand on the server) or SSG (the html is created at build time into a static html page).
What value does the prerendered document have for the client if it will be immediately thrown away?
The whole document is not thrown away if you see my first point. The value in SSR is if you want the initial greeting text to be something other than "Hello". For example, if you wanted your server to parse an auth token, get the user profile, and seed greeting with "Hello Jim" on page load, you'd prefer SSR. If you don't have any serverside processing prior to sending the html to the client, you could choose SSG.
Consider these two "pages":
// Using SSR
export default function MyPage({customerName}) {
return (<p>{customerName}</p>)
}
// Using SSG
export default function MyPage({customerName}) {
const [greeting, setGreeting] = React.useState("Hello")
React.useEffect(() => {
// Call server to get the customer's name
const name = myApi.get('/name')
setGreeting(`Hello ${name}`)
}, [])
return (<p>{greeting}</p>)
}
In the first example, the server renders the p tag with the customer name (coming from some process on the server) so the html source code will include that customer's name. Nothing is thrown out here.
In the second example, the site is built as html and that source code's p tag says "Hello". When the page is visited, useEffect runs and the p tag is updated when your api responds. So the user will see "Hello" for x microseconds and then it will switch to "Hello Jim".
Does this mean I unnecessarily have to "double render" some components on the client side?
No - you control what you render on the server versus on the client. If you seed a component with data on the server and don't change it on the client, it won't rerender.
In your example, yes - you're double rendering. But you may not need to. As you noted, window doesn't exist on the client. If you absolutely must show your "value pending..." line to your user before you get the window size, then you'll do double rendering - once to populate that string on the server and then once to replace it when react hydrates on the client.
If you don't need to show that pending line and just need to show the value on the client when window actually exists, you could rewrite it like this:
export default function Page() {
const [value, setValue] = React.useState();
React.useEffect(() => {
const newValue = window.innerWidth; // Some computations or subscriptions that depend on window
setValue(newValue)
}, []);
if(value) {
return <h1>{value}</h1>
}
return null
};
In this case, nothing is rendered on the server because there is no value on the server. Only when the client is hydrated will useEffect run, update value, and render your component one time.
I have a firebase database, that has a collection called "post" and in post there 6 variables (displayName, userName, verified, text, image, avatar). The idea is, there will be multiple posts in the database.
React Code:
const [posts, setPosts] = useState([]);
//Whenever the firebase database changes, it runs this method
useEffect(() => {
db.collection("posts").onSnapshot((snapshot) =>
//Loops through all the posts and adds the data into an array
setPosts(snapshot.docs.map((doc) => doc.data()))
);
}, []);
In react, I have two state variables, posts and setPosts. I'm assuming they are initially just set to empty arrays.
Now I have the useEffect function, that I am told runs whenever the database changes/updated. First question, how does the function know that the database updated? In other words, how does the useEffect function work?
Secondly, I'm pretty sure in the end, the post variable becomes a list of all the post objects in the database. I'm not sure how that happened. I have attached the code that updates this state above, but I'm not too sure how it works. Can you please break it down and explain how it works? I'm also not sure what the setPosts state is used for.
Please let me know!
In the above
In line 1 - You have used State hooks to set up posts as an empty Array. More reading can be done here to understand what state hooks mean - https://reactjs.org/docs/hooks-state.html
Next you set up a useEffect hook function (https://reactjs.org/docs/hooks-effect.html) to make a backend (firebase) api call after rendering.
Inside the hook function you are looking up data from the posts collection in firebase and bringing back a snapshot of all the documents in that collection. db.collection("posts").onSnapshot(callBack). The callback function is called every time something changes on the underlying database using well known observer pattern (read more in following links https://rxjs-dev.firebaseapp.com/guide/overview, https://firebase.google.com/docs/reference/node/firebase.firestore.CollectionReference#onsnapshot)
Then in the onSnapshot callback function you get an array containing documents which is further mapped to an output array using the javascript Map function snapshot.docs.map((doc) => doc.data()). https://www.w3schools.com/jsref/jsref_map.asp
Finally this output array is set in the posts variable using the
setPosts() method.
Hope this breakdown helps and I suggest reading the links in detail so its clear how everything comes together.
I am beginning to use React (hooks only), and facing a strange issue. I am trying to reproduce the problem in a small test code, but can't get it to happen, except in my full blown app. This leads me to wonder if I'm doing something really wrong.
I have an array of objects, declared as a state. I map this array to display its content. Except that nothing gets displayed (the array is filled, but nothing gets displayed). Now if I declare an un-related state, make it a boolean which flips each time my array gets updated, then my array gets displayed properly. As if, in the render phase itself, React did not detect the array's changes.
A few things:
the array gets updated by a socketIO connection, I simulate it here with a timer
I update my array OUTSIDE of my component function, BUT providing the setter function to the update function
I also create part of the render fields outside my component function (this has no effect, just for readability in my full app)
I essence, this is what I am doing:
const updateArray = (setTestArray, setTestTag, addArray) => {
setTestArray(prevTestArray => {
let newTestArray = prevTestArray.map((data, index) => (data + addArray[index]))
return newTestArray
})
setTestTag(prevTag => {
return (!prevTag)
})
}
const renderArray = (currentTestArray) => {
return currentTestArray.map((data, index) => (
<div>
testArray[{index}]={data}
</div>
))
}
function TestPage(props) {
const [testArray, setTestArray] = useState([])
const [testTag, setTestTag] = useState(false)
useEffect(() => {
let samples = 3
let initArray= []
for (let i=0; i<samples;i++) initArray[i] = Math.random()
setTestArray(initArray)
// In real code: setup socket here...
setInterval(() => {
let addArray= []
for (let i=0; i<samples;i++) addArray[i] = Math.random()
updateArray(setTestArray, setTestTag, addArray)
}, 1000)
return (() => {
// In real code, disconnect socket here...
})
}, []);
return (
<Paper>
Array content:
{renderArray(testArray)}
<br/>
Tag: {(testTag)? 'true' : 'false'}
</Paper>
)
}
This works just fine. But, in my full app, if I comment out everything concerning "testTag", then my array content never displays. testArray's content is as expected, updates just fine, but placing a debugger inside the map section show that array as empty.
Thus my questions:
is my updateArray function a bad idea? From what I read, my prevTestArray input will always reflect the latest state value, and setTestArray is never supposed to change... This is the only way I see to handle the async calls my socket connection generate, without placing "testArray" in my useEffect dependencies (thus avoiding continuously connecting/disconnecting the socket?)
rendering outside the component, in renderArray, doesn't affect my tests (same result if I move the code inside my component), but is there a problem with this?
As a side note, my array's content is actually more complex is the real app (array of objects), I have tried placing this in this test code, it works just fine too...
Thank you!
Edit: Note that moving updateArray inside the useEffect seems to be the recommended pattern. I did that in my full app. The hook linter does not complain about any missing dependency, yet this still doesn't work in my full app.
But the question is still whether what I am doing here is wrong or not: I know it goes against the guidelines as it prevents the linter from doing its job, but it looks to me like this will still work, the previous state being accessible by default in the setter functions.
Edit #2: Shame on me... silly mistake in my real app code, the equivalent of updateArray had a shallow array copy at some place instead of a deep copy.
Why adding the flipping tag made it work is beyond me (knowing the data was then indeed properly displayed and updated), but getting rid of this mistake solved it all.
I will leave this question on, as the question still stand: is placing the state update, and part of the rendering outside the component a functional problem, or just something which might mater on hide dependencies (preventing the react hooks linter from doing its job) and thus simply bad practice?
The fact is that things work just fine now with both functions outside the component function, which makes sense based on what I understand from hooks at this point.
I know Redux solves this but I came up with an idea.
Imagine I have an app that gets some JSON on start. Based on this JSON I'm setting up the environment, so let's assume the app starts and it downloads an array of list items.
Of course as I'm not using Redux (the app itself is quite simple and Redux feels like a huge overkill here) if I want to use these list items outside of my component I have to pass them down as props and then pass them as props again as deep as I want to use them.
Why can't I do something like this:
fetch(listItems)
.then(response => response.json())
.then(json => {
window.consts = json.list;
This way I can access my list anywhere in my app and even outside of React. Is it considered an anti-pattern? Of course the list items WON'T be changed EVER, so there is no interaction or change of state.
What I usually do when I have some static (but requested via API) data is a little service that acts kind like a global but is under a regular import:
// get-timezones.js
import { get } from '../services/request'
let fetching = false
let timez = null
export default () => {
// if we already got timezones, return it
if (timez) {
return new Promise((resolve) => resolve(timez))
}
// if we already fired a request, return its promise
if (fetching) {
return fetching
}
// first run, return request promise
// and populate timezones for caching
fetching = get('timezones').then((data) => {
timez = data
return timez
})
return fetching
}
And then in the view react component:
// some-view.js
getTimezones().then((timezones) => {
this.setState({ timezones })
})
This works in a way it will always return a promise but the first time it is called it will do the request to the API and get the data. Subsequent requests will use a cached variable (kinda like a global).
Your approach may have a few issues:
If react renders before this window.consts is populated you won't
be able to access it, react won't know it should re-render.
You seem to be doing this request even when the data won't be used.
The only downside of my approach is setting state asynchronously, it may lead to errors if the component is not mounted anymore.
From the React point of view:
You can pass the list from top level via Context and you can see docs here.
Sample of using it is simple and exists in many libraries, such as Material UI components using it to inject theme across all components.
From engineering concept of everything is a trade of:
If you feel that it's gonna take so much time, and you are not going to change it ever, so keep it simple, set it to window and document it. (For your self to not forget it and letting other people know why you did this.)
If you're absolutely certain they won't ever change, I think it's quite ok to store them in a global, especially if you need to access the data outside of React. You may want to use a different name, maybe something like "appNameConfig"..
Otherwise, React has a feature called Context, which can also be used for "deep provision" - Reference
I have the following code in my render method:
render() {
return (
<div>
{this.props.spatulaReady.ready() ? <p>{this.props.spatula.name}</p> : <p>loading spatula</p>}
</div>
)
}
Which according to my understanding, checks if the subscriptionhandle is ready (data is there) and displays it. If no data is available, it should display a simple loading message. However, when I first load the page this snippet is on, it get's stuck on the loading part. On a page reload the data (usually) displays fine.
If I check the spatulaReady.ready() when the page first loads and while the display is stuck on 'loading spatula', and the data that should be there, the handle reports as ready and the data is there like it is supposed to be. If I refresh the page it all displays fine as well. The problem is, this way of checking for data and rendering if it has arrived has worked fine for me in the past. Is it because the render method is not reactive? Because handle.ready() should be reactive.
What makes it even weirder is that it sometimes DOES correctly display the data on page load, seemingly at random.
CreateContainer code:
export default createContainer(props => {
return {
user: Meteor.user(),
spatulaReady: Meteor.subscribe('spatula.byId', props.deviceId),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
Publication code:
Meteor.publish('spatula.byId', function(deviceId) {
const ERROR_MESSAGE = 'Error while obtaining spatula by id'
if (!this.userId) //Check for login
throw new Meteor.Error('Subscription denied!')
const spatula = SpatulaCollection.findOne({_id: deviceId})
if(!spatula) //No spatula by this id
throw new Meteor.Error(403, ERROR_MESSAGE)
if(spatula.ownedBy != this.userId) //Spatula does not belong to this user
throw new Meteor.Error(403, ERROR_MESSAGE)
return SpatulaCollection.find({_id: deviceId})
})
I know I'm missing a piece of the puzzle here, but I've been unsuccessful at finding it. If you don't know the solution to my specific problem, pointing me in the right direction with another way of waiting for the data to arrive before displaying it is also greatly appreciated.
EDIT: After doing some trial-and-error and reading various other posts somewhat related to my project, I figured out the solution:
export default createContainer(props => {
const sHandle= Meteor.subscribe('spatula.byId', props.deviceId)
return {
user: Meteor.user(),
spatulaReady: sHandle.ready(),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
It still makes no sense to me that moving the ready() call to create container fixed all my problems though.
As you figured out, moving the .ready() call to createContainer fixes the problem. This is because Meteor reactivity only works when you call a reactive data source (a reactive function), such as collection.find() or subscriptionHandle.ready() within a reactive context, such as Tracker.autorun or createContainer. Functions within the React component, including render, are not reactive contexts from Meteor's perspective.
Note that React and Meteor reactivity are two different things. React's reactivity works simply so that whenever a component's props or state change, it's render function is re-run. It does not understand anything about Meteor's reactive data sources. Since createContainer (that is re-run by Meteor reactivity when reactive data sources in it change) simply passes props to the underlying component, the component is re-rendered by React when the props given from createContainer change.