0
export default function TopNav() {
  const ns = 'header';
  const { t, i18n } = useTranslation(ns);
  const data = getData(i18n.language, ns);

  var topNav = [];

  const parseData = () => {
    data.then((json) => {

      const headerArray = mkNumArray(json, 'row1');
      var topNav = headerArray.map((i) => {
        const url = t("row" + i + ".button" + i + ".url");
        const label = t("row" + i + ".button" + i + ".label");
        return (
         <a href={url}>{label}</a>
       );
      })
      console.log(topNav);
    });
  };

  parseData(topNav);
  console.log(topNav);

  return(
     ....
  )
}

I'm having trouble figuring out how to handle this nav variable. Because of the async request in getData, I want to have the variable set inside this parseData function, but then want to have it available for returning in the component. The console log inside ParseData is correct, but the one before return is empty. What's the best way to do this?

There's some external functions defined here, but I don't think they are relevant.

2
  • Why do you call parseData(topNav); when parseData itself does not have any parameters? Commented Sep 2, 2022 at 17:35
  • @SergeySosunov Good point, no reason, just left over from something else I tried. Commented Sep 2, 2022 at 17:41

3 Answers 3

1

You should read useState and useEffect.

The common flow is like this: make topNav a state of your component. its initial value may be empty. after the first time rendering, the parseData is triggered and set value for topNav hence trigger re-rendering your component with new value of topNav.

Your code may resemble below:

export default function TopNav() {

  const [topNav, setTopNav] = useState([]);

  useEffect(()=>{

    // assume getData is an async function
    const data = getData(i18n.language, ns);

    const parseData = () => {
     data.then((json) => {

      // ** do logic...
      
      setTopNav(/*value of topnav*/);
     });
    };
    
    parseData();

  }, []);  // this will run once 


  // if you want to run this effect every time something changed such as language
  // useEffect(()=>{}, [i18n.language] );

 
  return(
     ....
  )
}
Sign up to request clarification or add additional context in comments.

4 Comments

Just to point a few issues, 1) missing dependecy in [depsArray] of useEffect. For first render it will work but if something is changed - it will not update the data. Like if language is changed. 2) if getData is not a hook but is a function - it will return new promise on each component render, which is a bad thing and with (1) will cause an infinite rerendering problem.
@SergeySosunov oh I didn't read the question code carefully. so I think putting getData inside useEffect will be more appropriate. and if OP wants to run this effect every time, just remove the [].
No no, all is almost ok now, i was refering original code you had but as per what you have now - the only issue is useEffect will not trigger when i18n.language or ns change. ns looks like immutable const but im not sure about i18n.language, i guess it can be changed.
I just ended up ditching the functional component and using a full React component... Thanks everyone
1

Instead of using a variable as topNap use something like a "state variable". I mean, use something like setState within:

...
const me = this;

const parseData = () => {
    data.then((json) => {

      const headerArray = mkNumArray(json, 'row1');
      var topNav = headerArray.map((i) => {
        const url = t("row" + i + ".button" + i + ".url");
        const label = t("row" + i + ".button" + i + ".label");
        return (
         <a href={url}>{label}</a>
       );
      })

      me.setState(stateTopNavVar, topNav);
      console.log(topNav);
    });
  };

Sometimes with react it is also needed to use the setState callback. Well, this will depend on if you are using React classes or not. Remember, variables as state variables are the means used by React to propagate changes on the React Virtual DOM

5 Comments

Yeah, I was thinking of doing it this way, but didn't want to convert all my functional components to components that use state. Still wondering if I can avoid that, partially out of curiousity.
@PeterBreen state is the heart of Reactjs. why do you want to avoid it?
@PeterBreen Functional components use state all the time; what’s the particular issue with it? You may be using it indirectly through the other hooks anyway. Props and state are a primary way React knows what to do and when.
Functional components use state variables... just look a little bit different. I prefer React Classes...
Thanks for the redirection all, I was just curious if there was another way to do it, but it seems like state is the way to go.
1

Best way would be to modify getData to make it return a value, not a promise. Or you can create a custom hook that will do that for you. Like that is more "react" way. Next thing - do not declara variables and exeute functions in way you did. For states - useState or useMemo. For functions calls - useEffect.

Custom hook:

function useGetData(param1, param2) {
  // undefined = not loaded or error
  // null = empty response
  const [data, setData] = useState(undefined);
  const dataPromise = useMemo(() => {
    return getData(param1, param2);
  }, [param1, param2]);

  useEffect(() => {
    setData(undefined);
    if (!dataPromise) return;
    dataPromise
      .then((res) => setData(res || null))
      .catch((e) => {
        console.error(e);
        setData(undefined);
      });

    return () => setData(undefined);
  }, [dataPromise]);

  return data;
}

Usage:


function TopNav() {
  const ns = "header";
  const { t, i18n } = useTranslation(ns);
  const data = useGetData(i18n.language, ns);

  const topNav = useMemo(() => {
    if (!data) return [];

    const headerArray = mkNumArray(json, "row1");
    return headerArray.map((i) => {
      const url = t("row" + i + ".button" + i + ".url");
      const label = t("row" + i + ".button" + i + ".label");
      return <a href={url}>{label}</a>;
    });
  }, [data]);

  return <> what you had here </>;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.