What am I trying to do?
I'm trying to set an array of objects in which the value within the array is dependent on the parent ponent.
What is the code that currently tries to do that?
Here are the different files simplified:
// Parent.
export default function Parent() {
const [filePaths, setFilePaths] = useState();
useEffect(() => {
var fileContent = JSON.parse(fs.readFileSync("./config.json"); // Reading from a JSON.
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
setFilePaths(tempFilePaths); // Contents of "config.js" is now in the "useState".
}, []);
return (
<Child filePaths={filePaths}/>
)
}
// Child.
export default function Child({filePaths}) {
var links = [
{
path: filePaths[0].Link1,
},
{
path: filePaths[0].Link2,
},
]
return (
<div>Nothing here yet, but I would map those links to front-end links.</div>
)
}
// config.json
{
"url": "http:localhost:3000",
"FilePaths": [
{
"Link1": "C:\Documents\Something",
"Link2": "C:\Documents\SomethingElse"
}
]
}
When I render the "filePaths" in the return()
of the Child ponent, the "filePaths" is able to be rendered, but I wish to set the "filePaths" to the variable "links".
What do I expect the result to be?
I expect the variable "links" to be fine in the child ponent, being able to be used within the child ponent.
What is the actual result?
When starting the app I get a TypeError: Cannot read property '0' of undefined.
What I think the problem could be?
I think the child ponent renders without the parent ponent finishing the useEffect()
. I'm wondering if there's a way to tell the child ponent to wait for the parent ponent to finish, then proceed with setting the variable of "links".
What am I trying to do?
I'm trying to set an array of objects in which the value within the array is dependent on the parent ponent.
What is the code that currently tries to do that?
Here are the different files simplified:
// Parent.
export default function Parent() {
const [filePaths, setFilePaths] = useState();
useEffect(() => {
var fileContent = JSON.parse(fs.readFileSync("./config.json"); // Reading from a JSON.
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
setFilePaths(tempFilePaths); // Contents of "config.js" is now in the "useState".
}, []);
return (
<Child filePaths={filePaths}/>
)
}
// Child.
export default function Child({filePaths}) {
var links = [
{
path: filePaths[0].Link1,
},
{
path: filePaths[0].Link2,
},
]
return (
<div>Nothing here yet, but I would map those links to front-end links.</div>
)
}
// config.json
{
"url": "http:localhost:3000",
"FilePaths": [
{
"Link1": "C:\Documents\Something",
"Link2": "C:\Documents\SomethingElse"
}
]
}
When I render the "filePaths" in the return()
of the Child ponent, the "filePaths" is able to be rendered, but I wish to set the "filePaths" to the variable "links".
What do I expect the result to be?
I expect the variable "links" to be fine in the child ponent, being able to be used within the child ponent.
What is the actual result?
When starting the app I get a TypeError: Cannot read property '0' of undefined.
What I think the problem could be?
I think the child ponent renders without the parent ponent finishing the useEffect()
. I'm wondering if there's a way to tell the child ponent to wait for the parent ponent to finish, then proceed with setting the variable of "links".
-
filePaths
is undefined in the parent until it's set bysetFilePaths(tempFilePaths);
in theuseEffect
hook. It seems the consuming ponent assumes it's defined. Please share a plete code example that reproduces your issue. – Drew Reese Commented Aug 11, 2021 at 23:52
4 Answers
Reset to default 2filePaths
will be undefined
because you call useState()
with empty input.
There are two options (you can choose one) to solve this:
Initialize
filePaths
inside theuseState()
Return the
Child
ponent if thefilePaths
is not null/undefined.
export default function Parent() {
const [filePaths, setFilePaths] = useState();
useEffect(() => {
var fileContent = JSON.parse(fs.readFileSync("./config.json"); // Reading from a JSON.
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
setFilePaths(tempFilePaths); // Contents of "config.js" is now in the "useState".
}, []);
return (
// return the Child ponent if the filePaths is not null/undefined
{filePaths && <Child filePaths={filePaths}/>}
)
}
I personally prefer the second one because we can add a loading ponent when the filePaths
is still null/undefined.
You are right, that's why you should change your child ponent. It renders the filePaths whether it is defined or not.
Try to do as follows.
export default function Child({filePaths}) {
const [links, setLinks] = useState(filePaths);
useEffect(()=>{
setLinks(filePaths);
},[filePaths])
return (
<div>Nothing here yet, but I would map those links to front-end links.</div>
)
}
I think you're right on your guess about the sequence of methods calling:
According to this, when you use useEffect the method gets called after the rendering, as if it was a ponentDidMount lifecycle method, which is supported by the React official lifecycle diagram and React Documentation. And that is the reason because the props.filePaths whithin the Child ponent is undefined.
To avoid this, you should try to set an initial value (in the useState method).
something like the following (maybe extracting the repetition as a function):
// Parent.
export default function Parent() {
var fileContent = JSON.parse(fs.readFileSync("./config.json"); // Reading from a JSON.
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
const [filePaths, setFilePaths] = useState(tempFilePaths);
useEffect(() => {
var fileContent = JSON.parse(fs.readFileSync("./config.json"); // Reading from a JSON.
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
setFilePaths(tempFilePaths); // Contents of "config.js" is now in the "useState".
}, []);
return (
<Child filePaths={filePaths}/>
)
}
const [filePaths, setFilePaths] = useState();
will initialize filePaths as undefined.
so you can check firstly like
if (!filePaths) return null
in the parent;
or set initState in useState like
@see https://reactjs/docs/hooks-reference.html#lazy-initial-state
const [filePaths, setFilePaths] = useState(()=> {
var fileContent = JSON.parse(fs.readFileSync("./config.json");
var tempFilePaths = [];
fileContent.FilePaths.forEach((file) => {
tempFilePaths.push(file);
});
return tempFilePaths;
});