Passing React values from child to parent

Viewed 14187

I am struggling to work out something that is likely very simply. My parent component is a Search widget, which needs to use Filters defined in a separate Drawer component. Currently the user can type in a search query, which calls an API, and it needs to filter the results based on selectors form the drawer component. However, I cannot link the parent and child to make this happen!!

The parent component (which includes the '' component within):

function SuburbSearch(props: Props) {
    const classes = useStyles();
    const [value, setValue] = React.useState<App.Suburb | null>(null);
    const [inputValue, setInputValue] = React.useState('');
    ...
    
    return (
    <Autocomplete
      id="search"
      getOptionLabel={(option) => (typeof option === 'string' ? option : option.name + ' ' + option.state)}
      filterOptions={(x) => x}
      options={options}
      ...
      ...
      renderInput={(params) => (
        <TextField
          {...params}
          label="Search for a suburb"
          ...,
            startAdornment: (
              <InputAdornment position="start">
                <Filters></Filters>
              </InputAdornment>
            ),
            endAdornment: (
              <React.Fragment>
                {loading ? <CircularProgress color="inherit" size={20} /> : null}
                {params.InputProps.endAdornment}
              </React.Fragment>
            ),
          }}
          fullWidth
        />
      )}

The child component (this is the 'Filter'):

export default function Filters() {
  const classes = useStyles();
  const [state, setState] = useState(false);
  const [dollar, setDollars] = useState<number[]>([0, 20000000]);
  const [type, setType] = React.useState<string | null>('all');
  
  ...
  const list = (anchor: Anchor) => (
    <div className={classes.root} 
      role="presentation" /*onClick={toggleDrawer(false)} onKeyDown={toggleDrawer(false)}/*/>
      <Grid className={classes.main} container spacing={2}>
        <Grid className={classes.row} item xs={1} md={2} />
        <Grid className={classes.row} item xs={10} md={8}>
          <Typography className={classes.header} align='center' gutterBottom>
            Property Type
          </Typography>
          <ToggleButtonGroup value={type} onChange={handleTypeChange} exclusive>
            <ToggleButton value="all" >All</ToggleButton>
            <ToggleButton value="some" >Some</ToggleButton>
          </ToggleButtonGroup>
        </Grid>
        ...
      </Grid>
   </div>
}

2 Answers

You can do it simply by passing a function as a prop, like this:

parent component

childCallback(value) {
    // value passed from child
}

return {
    <ChildComponent passToParent={this.childCallback}/>
}

child component

this.props.passToParent(childValue);

Here I'm providing you with some basic state management technique in Pure React, because so far I came to know by discussing with you in comment section.

Pattern 1, straight forward

Let's say you have a Component, comp1 namely, and it has some props including a method that requires some data from its direct child component, let's say Comp2:

const Comp1 = ({ onActionOrChild }) => {
  return (
    <>
      <Comp2 onAction={onActionOrChild} />
    </>
  )
}

const Comp2 = ({ onAction }) => {
  return (
    <form onSubmit={onAction}>
      ...
    </form>
  )
}

In short, here we have a component that requires some data, let's say some form data, from one of its descendants, so we passed it down to that children. But the problem here is Prop drilling, Comp1 needs to receive a prop that it never uses.

Pattern 2, solving Prop Drilling with Context API

Now, react Context API is stable, which is very powerful to achieve something that used to require other state-management library.

Version 16.3 introduces a new context API that is more efficient and supports both static type checking and deep updates. - Docs

If we use context API, we can rip those methods out from the component and put them in a new file, that will create the context provider so that different component across the Application can communicate with each other, without passing down the props to the component a few level downs through the intermediate components that never use it.

Context.js

export const AppContext = createContext();

export AppContextProvider = ({children}) => {

  const onAction = () => {}
  return (
    <AppContextProvider.Provider
      value={{onAction}}
    >
    {children}
    </AppContextProvider.Provider>
  )
  
}

Now, if we need a method in any piece of the Application we need to just hook up the context API:

import { AppContext } from "./Context.js"

const comp3 = ({}) => {
  const { onAction } = useContext(AppContext);
  
  return (
    <form onSubmit={onAction}>
    ...
    </form>
  )
}

Related