DEV Community

william-luck
william-luck

Posted on

Tutorial: communication between sibling components using state, and controlled forms

Image description

Component hierarchy:

Parent Component: App.js
... Child component: ExampleForm.js
... Child component: SubmittedFormInfo.js
Enter fullscreen mode Exit fullscreen mode

Goal

I have an example form in the child component ExampleForm to receive input on a name, image url, and a price for a new item. I want this information to immediately be displayed in another child component SubmittedFormInfo, without the need to refresh the page.

Both of these components are children of App, but information cannot be directly passed between sibling components such as these. How can we take the information from the form, and then immediately display that information in the other component?

Walkthrough

This issue depends on the use of state and setter functions passed down as props to each of the components. Since information cannot be directly passed between sibling components, we need to make use of state in the parent component, which will pass down information to each of the two components so that the data can be displayed.

App component
Begin by utilizing the useState hook in the App component, and use an initial value of an empty object, which will eventually contain the information from our example form:

function App() {

const [newItem, setNewItem] = useState({})
Enter fullscreen mode Exit fullscreen mode

We are not too concerned with the actual value of newItem just yet. Instead, begin by passing down the setter function setNewItem to the ExampleForm component. The first goal here is that we want to change the value of newItem upon form submission using the setter function passed down:

<ExampleForm setNewItem={setNewItem}/>
Enter fullscreen mode Exit fullscreen mode
function ExampleForm({ setNewItem }) {
Enter fullscreen mode Exit fullscreen mode

ExampleForm component
Before going further, we need to use a controlled form to keep track of the data submitted by the user. For simplicity, declare three initial values as empty strings for each input field on the form using the useState hook:

function ExampleForm({ setNewItem }) {

  const [name, setName] = useState('')
  const [image, setImage] = useState('')
  const [price, setPrice] = useState('')
Enter fullscreen mode Exit fullscreen mode

These will be used as values for each input in the example form, as follows:

<form>
    <input type="text" name="name" placeholder="Name" value={name} />
    <input type="text" name="image" placeholder="Image URL" value={image} />
    <input type="number" name="price" placeholder="Price" value={price} />
    <button type="submit">Add Item</button>
</form>
Enter fullscreen mode Exit fullscreen mode

For controlled forms, every change that the user makes to the input field should update state to keep track on the information entered by the user. This is especially useful for immediately making use of the information, such as when you want matching items to display in the DOM using a search bar as the user types. Even though we only need this information upon submission, it is still helpful to use a controlled form. To make this form controlled, begin by declaring three separate functions to handle a change to each of the input fields. Within each function, we want to make use of the setName, setImage, and setPrice setter functions from the state in this component. Each function should update state using the event object, to access data on each letter entered to the form:

function handleNameChange(event) {
    setName(event.target.value)
  }

function handleImageChange(event) {
    setImage(event.target.value)
  }

function handlePriceChange(event) {
    setPrice(event.target.value)
  }
Enter fullscreen mode Exit fullscreen mode

To call these functions when the user inputs data, use these functions as callbacks for onChange events in each of the form input fields:

<form>
    <input type="text" name="name" placeholder="Name" value={name} onChange={handleNameChange}/>
    <input type="text" name="image" placeholder="Image URL" value={image} onChange={handleImageChange}/>
    <input type="number" name="price" placeholder="Price" value={price} onChange={handlePriceChange}/>
    <button type="submit">Add Item</button></form>
Enter fullscreen mode Exit fullscreen mode

The general idea is that as the user types, each of these functions will be called to update state. Since we are using state variables as the input values in the form, the form values will update as the state updates with the use of the handle functions. Once the user finishes typing, we will have complete information available to use in each of the name, image, and price state variables.

When the user submits the form, we want to change the value of newItem declared in App, using the information entered by the user. We can do this by calling the setter function setNewItem, which was passed down as a prop to the form component. Begin by declaring a handleSubmit function, which should be called when the user submits the form using onSubmit in the opening form tag. In the handleSubmit function, we want to create a new object, specifying key/value pairs using state variables as each value, as so:

const formData = {
      name: name,
      image: image,
      price: parseInt(price)
}
Enter fullscreen mode Exit fullscreen mode

Then call setNewItem, using the formData object as the specified value:

setNewItem(formData)
Enter fullscreen mode Exit fullscreen mode

Optionally, we can prevent a refresh of the page and set the form values back to empty strings to receive new data from the user. The final handleSubmit function should look something similar to this:

function handleSubmit(event) {
    event.preventDefault();

    const formData = {
      name: name,
      image: image,
      price: parseInt(price)
    }

    setNewItem(formData)

    setName('')
    setImage('')
    setPrice('')
 }
Enter fullscreen mode Exit fullscreen mode

The primary line of code to focus on here is setNewItem(formData). This updates state in the parent App component, which allows us to then pass that form data to SubmittedFormInfo as a child of App.

SubmittedFormInfo component
To finally display the form data in our application, in the App component, pass down newItem as a prop to SubmittedFormInfo:

<SubmittedFormInfo newItem={newItem}/>
Enter fullscreen mode Exit fullscreen mode

newItem now contains an object with the name, image url, and price of the item added by the user. Have SubmittedFormInfo receive the prop, and optionally destructure newItem to more easily use the information contained in the newItem object.

function SubmittedFormInfo({ newItem }) {

  const {name, image, price} = newItem
Enter fullscreen mode Exit fullscreen mode

All that is left to do is display the name, image, and price variables in the DOM:

return (
    <header>
      <h2>
        Submitted Form Data
      </h2>
      <p>Name: {name}</p>
      <p>Image url: {image}</p>
      <p> Price: ${price}</p>
    </header>
  );
}
Enter fullscreen mode Exit fullscreen mode

With this addition, once the user submits the form, the information entered by the user should now display automatically in the DOM. This occurs because of state updates. Since SubmittedFormInfo depends on the variable newItem in state, once the value of newItem updates, this will cause the SubmittedFormInfo component to re-render, immediately displaying the information entered by the user.

Conclusion

We used newItem and its setter function to update the application. We began by passing down 'setNewItem' to ExampleForm, which was called when the user submitted the form. As the user typed, state in the form component updated, keeping track of the values entered by the user. Upon submission, we set the value of newItem to the data entered by the user. This caused a state update for newItem, which was passed down to our display container as a prop. This component then re-rendered upon submission, displaying the information entered by the user immediately below the form, with no need to refresh the page.

Top comments (0)