简体   繁体   中英

Using React Hooks, when I pass down a prop from parent to a child component, the prop in the child component is undefined

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 component.

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 component, 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 component, being able to be used within the child component.

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 component renders without the parent component finishing the useEffect() . I'm wondering if there's a way to tell the child component to wait for the parent component to finish, then proceed with setting the variable of "links".

filePaths will be undefined because you call useState() with empty input.

There are two options (you can choose one) to solve this:

  1. Initialize filePaths inside the useState()

  2. Return the Child component if the filePaths 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 component if the filePaths is not null/undefined
        {filePaths && <Child filePaths={filePaths}/>}
    )
}

I personally prefer the second one because we can add a loading component when the filePaths is still null/undefined.

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 componentDidMount 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 component 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}/>
  )
}

You are right, that's why you should change your child component. 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>
  )
}

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.org/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;
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM