0

I have a Quiz block that contains Questions blocks that have Answer blocks. Both Quiz and Question use InnerBlocks to allow questions or answers to be dragged around, edited and saved.

When I save the Quiz, how can I get the index of each question in question/save.js and the index of each question and answer in each answer/save.js.

I would like this so I can give all checkbox unique ids, like

<input id="q1-a3" />

And label the questions things like "Question 1."

Quiz/save.js

return (
  <div {...blockProps}>
    <InnerBlocks.Content />
  </div>
);

Question/save.js

return (
  <div {...blockProps}>
    <h3>Question {numToWords(1)}</h3>
    <ul class="quiz--answers">
      <InnerBlocks.Content />
    </ul>
  </div>
);

Answer/save.js

return (
  <li {...blockProps}>
    <input id="checkbox-2" type="checkbox" value="checkbox-2" />
    <label class="checkbox" for="checkbox-2">
      {JSON.stringify(attributes)}
      {attributes.text}
      <span class="quiz--answer-content">{attributes.explanation}</span>
    </label>
  </li>
);

I have tried using getBlockIndex( clientId ) to try to get the index of each block, however I'm not sure where to use this as it always returns -1. It also only seems that clientId is available in the edit function, and I think I want it in the save function unless I go about adding it to the block attributes.

3 Answers 3

1

You may now retrieve the index of a inner block via the core/block-editor data module. You may pair it with the useSelect hook to keep the indexes up to date on changes.

Something like this. Intentionally verbose for clarity.

import {useSelect} from '@wordpress/data';

const Answer = ( blockProps ) => {
    const {index} = useSelect( select => {
        const {getBlockIndex} = select( 'core/block-editor' );
        return {
            index: getBlockIndex( blockProps.clientId );
        }
    } );

    return ( <li {...blockProps}>
        <input id="checkbox-2" type="checkbox" value="checkbox-2" />
        <label className="checkbox" htmlFor="checkbox-2">
            {JSON.stringify( blockProps.attributes )}
            {index}
            <span className="quiz--answer-content">{blockProps.attributes.explanation}</span>
        </label>
    </li> );
}

0

try saving the values in html data attributes. then get them using getAttribute("data-index")

data-index="number"

usually in save.js you can't use useStates and useEffects. you have to make another file that uses vanilla js to do the logic. save.js is saved as a pure html elements.

9
  • How would I get those values?
    – Djave
    Commented Nov 10, 2021 at 18:34
  • save.js most not contain any logic. As I told, you use vanllia js in another file. And use document.querySelector to grap the element, then get its data attribute using element.getAttribute("data-index").
    – shadi
    Commented Nov 10, 2021 at 18:38
  • If you want more help, and exactly how to do it. i need to see all your code. edit.js and save.js....
    – shadi
    Commented Nov 10, 2021 at 18:41
  • But usually when you want to add some functionality to your save.js, you use vanilla js in the traditional way. Not through react. Though, it is possible to use React in the frontend. But that is a different topic.
    – shadi
    Commented Nov 10, 2021 at 18:46
  • 1
    Hey @shadi, thanks for the advice on the Edit function. I think we were talking about two different things, but your tip on setting everything in there (no logic in save) was able to help me do what I needed to and I have answered my own question.
    – Djave
    Commented Nov 11, 2021 at 15:57
0

The Quiz block was the top level block. So in its Edit function, I created a subscribe method. I was able to select the editor and get the current block:

// Subscribe to all changes of the Gutenberg blocks
wp.data.subscribe(() => {
  const quiz = wp.data.select("core/editor").getBlock(clientId);
  triggerUpdate(quiz);
});

The subscribe method is covered briefly here:

https://www.youtube.com/watch?v=OQczO6VOMkY

This subscribe() happened to be called thousands of times as the page was edited, so I was unable to work out a better way of working with it than to manually "debounce" it:

// Debounce that change, so it wouldn't trigger thousands of times
let timeouts = [];

const triggerUpdate = (quiz) => {
  while (timeouts.length) {
    clearTimeout(timeouts.shift());
  }

  const timeout = setTimeout(() => {
    // ... the code to get the index of each block
  }, 1000);
  timeouts.push(timeout);
};

Now I had a function triggering every time I updated the post, I just needed to actually get the indexes. This was easily done by using .innerBlocks, once on the quiz (which I'd already got by using wp.data.select("core/editor").getBlock(clientId)) and then once on each question.

With that, I could loop through each block and update its attributes using updateBlockAttributes

// Loop through all nested blocks and update relevant attributes
quiz.innerBlocks.forEach((question, q) => {
  let qid = "q" + (q + 1); // qid = "q1", "q2" etc
  if (question.attributes.qid != qid) {
    const update = {
      qid,
      questionTitle: "Question " + numToWords(parseInt(q + 1)),
    };
    console.log(update);
    dispatch("core/editor").updateBlockAttributes(
      question.clientId,
      update
    );
  }
  question.innerBlocks.forEach((answer, a) => {
    let aid = qid + "a" + (a + 1); // aid = "q1a2" etc
    if (answer.attributes.qid != qid) {
      dispatch("core/editor").updateBlockAttributes(answer.clientId, {
        aid,
      });
    }
  });
});

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.