reactjsreact-contextpreactfreshjs

How to send data from a child component to its sibling?


I want create an app with two forms, each form in its own component. Users type in form 1, then the data will be passed to component 2. In there it will join the data of what they type in form 2. How can I do this in Fresh (Preact)?

Here is my attempt when following the first answer in How to update React Context from inside a child component?:

index.jsx

import { createContext } from "preact";
import { Form1Context } from "../islands/Context.jsx";

function App() {
  return (
    <Form1Context.Provider value="dark">
      <Form1 />
      <Form2 />
    </Form1Context.Provider>
  );
}

Context.js

import { createContext } from 'preact'

export const Form1Context = createContext({
    form1Data: 'Form 1 placeholder',
    setInput: () => {} 
}) 

export function Form1Provider({ children }) {
    const [form1Data, setInput] = useState('Form 1 placeholder');

    return (
        <Form1Context.Provider value = {form1Data} >
            {children} 
        </Form1Context.Provider>
    )
} 

Form1.jsx

import { useContext, useState } from "preact/hooks";

export default function Form1(props) {
  const {form1Data, setForm1Data } = useContext(Form1Provider)
    
  return (
    <textarea 
    value = {form1Data} 
    onInput={(event) => setForm1Data(event.target.value)}
    />
  );
}

Form2.jsx

import { useContext, useState } from "preact/hooks";

export default function Form2(props) {
  const {form1Data, setForm1Data } = useContext(Form1Provider)
  const {form2Data, setForm2Data } = useState('Form 2 placeholder')
    
  return (
    <>
        <textarea 
            value = {form2Data} 
            onInput={(event) => setForm2Data(event.target.value)}
        />
        <p>The value is {form1Data} + {form2Data}</p>
    </>
  );
}

The app compiles fine, but the actual result is empty.


Solution

  • You're not using the provider wrapper you created (with the form1 state in it):

    index.jsx

    function App() {
      return (
        <Form1Provider>
          <Form1 />
          <Form2 />
        </Form1Provider>
      );
    }
    

    The context is declared in a strange way, I suggest:

    Context.jsx

    export const Form1Context = createContext(null);
    
    export function Form1Provider({ children }) {
      const form1State = useState('Form 1 placeholder');
      return (
        <Form1Context.Provider value={form1State}>{children}</Form1Context.Provider>
      );
    }
    

    When calling useContext you need to use the context, not the provider. Use square brackets in this case since we return the result of useState.

    Form1.jsx

    export default function Form1(props) {
      const [form1Data, setForm1Data] = useContext(Form1Context);
    
      return (
        <textarea
          value={form1Data}
          onInput={(event) => setForm1Data(event.target.value)}
        />
      );
    }
    

    You're also misusing useState - you need to destructure with square brackets since it returns an array:

    Form2.jsx

    export default function Form2(props) {
      const [form1Data] = useContext(Form1Context);
      const [form2Data, setForm2Data] = useState('Form 2 placeholder');
    
      return (
        <>
          <textarea
            value={form2Data}
            onInput={(event) => setForm2Data(event.target.value)}
          />
          <p>
            The value is {form1Data} + {form2Data}
          </p>
        </>
      );
    }
    

    Stackblitz demo with react: https://stackblitz.com/edit/stackblitz-starters-yyjto3?file=src%2FApp.tsx