Inverse Data Flow in React (Children, go talk to your parents…)

Jon Brundage Jr.
Nerd For Tech
Published in
5 min readMay 31, 2021

--

When starting to work with React and useState, a common problem I had was making sure the DOM was being rendered properly when different components interacted with each other. Ensuring that a parent component receives information from its children components is called inverse data flow, and without it, your DOM’s state will not render properly.

Say you have an array of items on a page. On that page you can see a list of all your items as individual cards, and you have an ability to search within those items. Since you are organized and like to give each functionality of your app its own component, your component model in terms of parent to children relationships would look something like this:

So here is our hypothetical relationship of a parent component with its children (and grandchild) components. The React convention on deciding where to have data live is to fetch it in the lowest common parent model so that each child component that depends on that information has access to it.

In React, we have the wonderful tool of passing properties, or props, from parent components to children. These can be any JS object we want, most usually an array. In this instance, we’ll be passing down arrays, objects and most importantly functions. If you aren’t too familiar with this concept, head here to read up on it before reading on.

Since the search and list components depend on the same array, the Main Page will be where we call on that information from the backend, like this:

import {useEffect, useState} from 'React'
import Search from './Search'
import ListOfItems from './ListOfItems'
function MainPage(){
const [array, setArray] = useState([])
useEffect(() => {
fetch('https://www.backend.URL')
.then(res => res.json())
.then(data => setArray(data))
}, [])
return (
<div>
<Search />
<ListOfItems array={array} />
</div>
)
}

Within our List of Items component, we will map through that array given as a prop from the parent component and pass down an object from that array to the Item Card, like this:

import ItemCard from './ItemCard'function ListOfItems({array}){
const itemArray = array.map(itemObject =>
<ItemCard key={itemObject.id} item={itemObject} />
)
return (
<div>
{itemArray}
</div>
)
}
export default ListOfItems

And finally our on Item Card, we will have the information for each item displayed, like this:

function ItemCard({item}){
return(
<div>
<h2>item.name</h2>
<p>item.content</p>
<button>Delete this item!</button>
</div>
)
}
export default ItemCard

The delete button will affect both the List of Items component state, and the Search component state, but how do we get the child component (Item Card) to tell its parent what it’s done to make sure the DOM renders correctly? We don’t want our user to see items that have been deleted, or to search through them either. Enter our friend, the callback.

When a user clicks that delete button, two things need to happen. First, we need to make a fetch to our backend in the form of a delete method to actually delete that item from our database. That will happen in our Item Card component. Second, that Item Card component needs to tell its parent (or grandparent in this case) that the item has been deleted and to update the DOM accordingly. To do this, we will define a function in the Main Page component to handle the deletion and pass it down as a prop to be invoked by the Item Card on deletion.

import {useEffect, useState} from 'React'
import Search from './Search'
import ListOfItems from './ListOfItems'
function MainPage(){
const [array, setArray] = useState([])
useEffect(() => {
fetch('https://www.backend.URL')
.then(res => res.json())
.then(data => setArray(data))
}, [])
function onDelete(id){
const updatedArray = array.filter(item =>
item.id !== id
)
setArray(updatedArray)
}
return (
<div>
<Search />
<ListOfItems array={array} onDelete={onDelete}/>
</div>
)
}

Here, onDelete is given an ID and searches through our original array. It then excludes any items whose IDs match the given ID and returns a new array. Notice we do not invoke this function in Main Page. We are simply defining it here. It then gets passed through the List of Items component to the Item Card where the button lives.

import ItemCard from './ItemCard'function ListOfItems({array, onDelete}){
const itemArray = array.map(itemObject =>
<ItemCard key={itemObject.id} item={itemObject}
onDelete={onDelete}/>
)
return (
<div>
{itemArray}
</div>
)
}
export default ListOfItems

Finally, we invoke this onDelete function within our fetch method when we are figuring out what to do with the response from that fetch.

function ItemCard({item, onDelete}){
const {id, name, content} = item
function handleClick(){
fetch(`https://www.backend.URL/${id}`, {
method: "DELETE",
})
.then(res => res.json())
.then(() => onDelete(id))
}
return(
<div>
<h2>{name}</h2>
<p>{content}</p>
<button onClick={handleClick}>Delete this item!</button>
</div>
)
}
export default ItemCard

Notice here we are also destructuring our Item object prop to make the ID accessible for the fetch. Without this, our handleClick function will not know which item you are trying to delete. Now what about that Search component you ask? Let’s do all this again. This will get slightly more complicated, but stick with me. You can do this!

Starting with in the Main Page component, we’ll define how we want the Search component to affect the DOM. Also we need to map through a filtered array instead of our original backend array, so we’ll make a variable for that and pass that as a prop to our List of Items component.

import {useEffect, useState} from 'React'
import Search from './Search'
import ListOfItems from './ListOfItems'
function MainPage(){
const [array, setArray] = useState([])
const [searchedItem, setSearchedItem] = useState("")
useEffect(() => {
fetch('https://www.backend.URL')
.then(res => res.json())
.then(data => setArray(data))
}, [])
function onDelete(id){
const updatedArray = array.filter(item =>
item.id !== id
)
setArray(updatedArray)
}
function onSearch(e){
setSearchedItem(e)
}
const searchedArray = array.filter(item => {
if(item.name.toLowerCase().includes(searchedItem.toLowerCase())){
return true
}
})
return (
<div>
<Search searchedItem={searchedItem} onSearch={onSearch}/>
<ListOfItems array={searchedArray} onDelete={onDelete}/>
</div>
)
}

Here we have to include a second useState so React knows what the user is searching for. Just like with our delete button, we are defining onSearch in our parent and invoking it in our child.

function Search({searchedItem, onSearch}){
return(
<div>
<input type="text"
value={searchedItem}
onChange={e => onSearch(e.target.value)}>
</input>
</div>
)
}
export default Search

What that onSearch is doing is setting the value of what the user typed and sending it back up to the Main Page, thus React can figure out what to filter our original array against. The results of this will be that your List of Items will change in real time on your page as the user types in what they’re looking for. Pretty sweet!

Another way of remembering this work flow, as I’ve been told by a wonderful instructor, is that with parent and children components, you should define down and invoke up. We defined the onDelete and onSearch in the Main Page (parent) component, and then sent it down to the children. When they are invoked (onDelete in Item Card or onSearch in Search), that action is sent back up to the parent.

Hopefully this helped clear up some frustration when using state with DOM manipulation. And if not, here are some links to some helper topics this post relied on.

What is the DOM?

React useState Hook

React useEffect Hook

--

--

Jon Brundage Jr.
Nerd For Tech

Brooklyn-based Software Engineer specializing in React and Javascript