Home > Blockchain >  How to update state while in a child component?
How to update state while in a child component?

Time:10-13

I have a component called Blocks that gets data from the backend and maps them all to a child component called Block. I want to be able to update the Block name from within the child but how would I get this change to occur in the parent?

Blocks.js

const Blocks = ({ updateBlocks }) => {
    const [blocks, setBlocks] = useState([])

    useEffect(() => {
        const getBlocks = async () => {
            const blocksFromServer = await fetchBlocks()
            setBlocks(blocksFromServer)
        };

        getBlocks()
    }, []);

    const fetchBlocks = async () => {
        const res = await fetch("/api/getblocks")
        const data = await res.json()

        return data
    }

    return (
        <form onSubmit={updateBlocks}>
            {blocks.map((block) => (
                <Block key={block.id} block={block} />
            ))}

            <input type="submit" value="update"></input>
        </form>
    )
}

export default Blocks

Block.js

import React from "react"
import { useState } from "react"

const Block = ({ block }) => {
    const [topic, setTopic] = useState('')

    return (
        <div className="block">
            <input type="text" name="topic" value={block.topic} onChange={}></input>
            <h4>{block.start_time} - {block.end_time}</h4>
            <hr  />
        </div>
    )
}

export default Block

CodePudding user response:

There are many ways to handle shared state, though for a minimal change in your code, you could pass the child Block components an update function to use:

const handleNameChange = useCallback((blockId, newName) => {
    const updatedBlocks = blocks.map((block) => {
        if (block.id === blockId) {
            block.name = name;
        }

        return block;
    });
  
    setBlocks(updatedBlocks);
}, [blocks]);

// ...

{blocks.map((block) => (
    <Block key={block.id} block={block} onNameChange={handleNameChange} />
))}

In Block:

const { block, onNameChange } = props;

const handleOnChange = useCallback((event) => {
  onNameChange(block.id, event.target.value);
}, [block.id]);

<input type="text" name="topic" value={block.topic} onChange={handleOnChange} />

CodePudding user response:

You "lift state up" by passing down a reference to a function that updates state into Block. When an input changes its listener calls the function. We grab the name and the value from the input, and update the state with the new data.

For convenience I'm passing in a sample data set.

const { useState } = React;

function Blocks({ data }) {

  // Initialise state with the data
  const [ blocks, setBlocks] = useState(data);

  // The handler destructures the name and value from
  // the changed input, copies the state, finds the index of the
  // block, updates that object's topic, and then updates
  // the state
  function handleChange(e) {
    const { name, value } = e.target;
    const copy = [...blocks];
    const index = copy.findIndex(block => block.name === name);
    copy[index].topic = value;
    setBlocks(copy);
  }

  // Shows the updated state
  function handleClick() {
    console.log(JSON.stringify(blocks));
  }

  // Pass the handler down to each
  // block component
  return (
    <div>
      {blocks.map(block => {
        return (
          <Block
            key={block.id}
            block={block}
            handleChange={handleChange}
          />
        );
      })}
      <button onClick={handleClick}>View state</button>
    </div>
  );

}

// `Block` accepts the data, and the handler reference,
// adds the reference to the `onChange` listener, and the
// block topic to the value (controlled component)
function Block({ block, handleChange }) {
  return (
    <div className="block">
      <label>Topic</label>
      <input
        type="text"
        name={block.name}
        placeholder="Add topic"
        value={block.topic}
        onChange={handleChange}
      />
    </div>
  );
}

const data = [
  { id: 1, name: 'block1', topic: 'topic1' },
  { id: 2, name: 'block2', topic: 'topic2' },
  { id: 3, name: 'block3', topic: 'topic3' }
];

ReactDOM.render(
  <Blocks data={data} />,
  document.getElementById('react')
);
label { margin-right: 0.2em; }
button { margin-top: 1em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

CodePudding user response:

Blocks.js

const Blocks = ({ updateBlocks }) => {
    const [blocks, setBlocks] = useState([])

    useEffect(() => {
        const getBlocks = async () => {
            const blocksFromServer = await fetchBlocks()
            setBlocks(blocksFromServer)
        };

        getBlocks()
    }, []);

    const fetchBlocks = async () => {
        const res = await fetch("/api/getblocks")
        const data = await res.json()

        return data
    }

    return (
        <form onSubmit={updateBlocks}>
            {blocks.map((block, i) => (
                <Block onBlockChange={(newBlock) => setBlocks(blocks => blocks.splice(i, newBlock))} key={block.id} block={block} />
            ))}

            <input type="submit" value="update"></input>
        </form>
    )
}

export default Blocks

Block.js

import React from "react"
import { useState } from "react"

const Block = ({ onBlockChange, block }) => {
    return (
        <div className="block">
            <input type="text" name="topic" value={block.topic} onChange={(e) => onBlockChange({ topic: e.target.value, ...block})}></input>
            <h4>{block.start_time} - {block.end_time}</h4>
            <hr  />
        </div>
    )
}

export default Block
  • Related