Home > Software engineering >  React - Passing data from child to parent component
React - Passing data from child to parent component

Time:04-10

I'm building a React app with class components and I'm really stuck on one problem. I made an app that initially renders a search form, fetches some data based on what the user typed in the form, and displays them in a table. Everything works fine and I'm happy. The problem comes when I introduce a footer; I want, for style purposes, make the footer always stay at the bottom of the page. At the start, there's only the search form and there's no table; if I set the CSS style of the footer to "position: sticky" there's no way to make it stay at the bottom. It needs to be in fixed position. But when the table renders, the fixed position does not work because the table might be very long and gets rendered over the footer. So I need to change the CSS of the footer dynamically: the solution I came up with is that I should create two styles in the CSS file for the same footer, one with position fixed and the other is the same but with the sticky position. I would then need to change the id of the footer before and after the table is rendered (to trigger the right "position"), and I'm capable of doing that in React. Basically, I have to tell React: "when the table is not rendered in the app, set footer's id to "fixed" in order to trigger the fixed position CSS style. When the user fetches the data, the table is rendered and the footer's id must then be changed to "sticky". The only problem is: I don't know how to do it in the hierarchy of my app's components. As of now, the footer is rendered in the App.js component, which is:

App.js

import './App.css';
import { ButtonFetcher } from './components/ButtonFetcher';  // tutte importazioni non default, quindi 
import { MapEntry } from './components/MapEntry';  // vanno fatte tutte con il nome classe tra parentesi {}.
import { Header } from './components/Header';
import { ButtonMatches } from './components/ButtonMatches';
import { CustomFooter } from './components/CustomFooter';

function App() {
  return (
    <div className="App">
      <Header />
      <ButtonMatches />
      <CustomFooter />
    </div>
  );
}

export default App;

As you can see, App.js renders a header, the search form (ButtonMatches) and the CustomFooter. Now, inside ButtonMatches I implemented the fetch() logic, and the component renders the MatchTable component, which is null if the data is non-existent (user didn't do the search), and it's a full html table if the user did do the search.

ButtonMatches.js

// (some code ...)

render() 
    {       
        // (some code ...) 
        
        return (
                <div>
                  <div>
                    <form className="PlayerSearch" onSubmit={(event) => { event.preventDefault(); } }>
                        <input type="search" id="search-recent-matches" name="Gamertag" placeholder="Enter a Gamertag..."></input>
                        <button type="submit" onClick={ this.onClickNewSearch } >Search</button>
                    </form>
                  </div>
                  { WLKDcounter }
                  <MatchTable recentMatches={ this.data }/>
                  { more }
                </div> 
            );    
    }

So, now the problem is: how do I tell CustomFooter that the table is rendered? Right now, the CustomFooter is rendered in the App component, which is the parent component of ButtonMatches, which is the parent component of MatchTable. So basically, the MatchTable component must be able to say to its parent component ButtonMatches "Hey! The table is not null and it's rendered!"; then the ButtonMatches component must say to its parent component App "Hey! The table is not null and it's rendered!". Then the App component must change the props of the CustomFooter component in order to change its state and therefore making him change its id and style.

The very first problem is that sending data from child to parent it's a React anti-pattern, and I really don't know how to avoid this. Is rendering the footer into the MatchTable component the only solution, to comply to the unidirectional data flow of React? Conceptually, it looks so ugly to me that the table component renders the footer component. I also would need the very same footer in other different pages of my website.

The second problem is that if I leave the footer inside the App component, and find a way (probably, through function calls) to notify the parents of the table that it's rendered, I would fall in something like "reverse prop drilling" since ButtonMatches would get a useless info from its child MatchTable that it only needs to pass to its parent App component. Since I don't plan of using hooks right now (useContext would be helpful if the prop drilling was not "reversed"), I would break a different React design pattern.

I can't find any solution to this, and my web designer friend already tried everything she could to make the style change, but to no avail. She told me that she absolutely needs the "position" attribute of the footer to change before and after the table is rendered in order to always make the footer stay at the bottom of the page in both situations.

CodePudding user response:

Basically you have to control your Footer from its parent.

So in your App component, you can add a new state property like isDataVisible. You need to pass its setter to your ButtonMatches and its value to your Footer to control its own styles.

It will be something like this:

const [isDataVisible, setIsDataVisible] = useState()

...

<ButtonMatches onToggleDataVisible={setIsDataVisible} />

...

<CustomFooter isSticky={isDataVisible} />

Hope it helps

CodePudding user response:

You need to define a hook and pass the state update function as a prop for example

function App() {
const [data,setData] = useState(/* initial value here */)

  return (
    <div className="App">
      <Header />
      <ButtonMatches updateValue={()=>setValue(val)}/> //call this.props.updateValue(inputState) at the component
      <CustomFooter value={value}//> //send value to footer
    </div>
  );
}
  • Related