useState updates state on second click - reactjs

I've seen that this issue has been asked many times before but none of the answers make sense to me. After trying about every solution i could find, decided i'd just ask it myself.
Below is my code, the main issue being that in the DOM filteredData only changes on second click. Note, projs is the prop containing data fetched that gets filtered out and displayed
const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
const [projectData, setProjectData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [category, setCategory] = useState("");
useEffect(() => {
setProjectData(projs);
}, []);
const handleCategory = (res) => {
setCategory(res.data);
const filter = projectData.filter((data) => data.category === category);
setFilteredData(filter);
};
button:
{langs.map((data, index) => (
<button
key={index}
class="px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"
onClick={() => handleCategory({ data })}
>
{data}
</button>
))}
I'm pretty lost at the moment, any help would be greatly appreciated

You can't use the newly set value of a state in the same render cycle. To continue calling both setState calls in the same handler you need to use the category from res.data to generate the new filtered array and then set both.
const handleCategory = (res) => {
const filter = projectData.filter((data) => data.category === res.data);
setCategory(res.data);
setFilteredData(filter);
};
const { useState, useEffect } = React;
function App({ projs }) {
const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
const [projectData, setProjectData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [category, setCategory] = useState("");
useEffect(() => {
setProjectData(projs);
}, [projs]); // need to watch for change as props won't update this automatically. Setting state from props is somewhat of an anti-pattern
const handleCategory = (res) => {
const filter = projectData.filter((data) => data.category === res.data);
setCategory(res.data);
setFilteredData(filter);
};
return (
<div className="App">
{langs.map((data) => (
<button
key={data} // avoid using index as key as this will lead to erros in updating.
class={(data === category ? 'selected ' : '') + "px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"}
onClick={() => handleCategory({ data })}
>
{data}
</button>
))}
<div>
<h3>{category}</h3>
{filteredData.length
? filteredData.map(p => (
<div>
<p>{p.value} – {p.category}</p>
</div>
))
: <p>No projects match</p>}
</div>
</div>
);
}
const projectData = [{ id: 1, value: 'Project 1', category: 'react' }, { id: 2, value: 'Project 2', category: 'next js' }, { id: 3, value: 'Project 3', category: 'react' }, { id: 4, value: 'Project 4', category: 'material-ui' }]
ReactDOM.render(
<App projs={projectData} />,
document.getElementById("root")
);
.selected {
background-color: tomato;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Alternatively, update filteredData in a useEffect that watches for changes to either category or projectData
useEffect(() => {
setFilteredData(projectData.filter((data) => data.category === category));
}, [category, projectData]);
const handleCategory = (res) => {
setCategory(res.data);
};
const { useState, useEffect } = React;
function App({ projs }) {
const langs = ["react", "next js", "material-ui", "tailwind css", "firebase"];
const [projectData, setProjectData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [category, setCategory] = useState("");
useEffect(() => {
setProjectData(projs);
}, [projs]); // need to watch for change as props won't update this automatically. Setting state from props is somewhat of an anti-pattern
useEffect(() => {
setFilteredData(projectData.filter((data) => data.category === category));
}, [category, projectData]);
const handleCategory = (res) => {
setCategory(res.data);
};
return (
<div className="App">
{langs.map((data) => (
<button
key={data}
class={(data === category ? 'selected ' : '') + "px-2 sm:px-6 py-2 ring-2 font-semibold ring-portfBtnLight rounded-md transform hover:scale-110 transition duration-500"}
onClick={() => handleCategory({ data })}
>
{data}
</button>
))}
<div>
<h3>{category}</h3>
{filteredData.length
? filteredData.map(p => (
<div>
<p>{p.value} – {p.category}</p>
</div>
))
: <p>No projects match</p>}
</div>
</div>
);
}
const projectData = [{ id: 1, value: 'Project 1', category: 'react' }, { id: 2, value: 'Project 2', category: 'next js' }, { id: 3, value: 'Project 3', category: 'react' }, { id: 4, value: 'Project 4', category: 'material-ui' }]
ReactDOM.render(
<App projs={projectData} />,
document.getElementById("root")
);
.selected {
background-color: tomato;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
As noted in the snippets –
Setting state from props is a bit of anti-pattern, but if you must you should add the prop to the dependency array so that state will be updated should the prop change. (prop changes don't force remounting).
Also, avoid using index as key, especially when mapping arrays whose order/membership will change as React will not update as expected.

I think this is the issue
setCategory(res.data);
const filter = projectData.filter((data) => data.category === category);
You invoke setCategory. But that won't change the immediate value of category that you reference in the subsequent line.
I think you want this:
const updatedCategory = res.data;
setCategory(updatedCategory);
const filter = projectData.filter((data) => data.category === updatedCategory);
Also, is res.data actually the "category" thing you want to use? I'm just asking because you use data between so many variables, parameters, and object members, I can't help but to wonder if it's really a typo.

Related

Nested state (react) with onclick events

I was given the task to build a recursive tree with some functionality. I managed to build a tree using a recursive component, I attach the code below.
function App() {
const [data, setData] = useState([{
id: 1,
name: "node 1",
children: [{
id: 2,
name: "node 1.1",
children: [{
id: 8,
name: "node 1.1.1",
children: []
}]
},{
id: 3,
name: "node 1.2",
children: [{
id: 4,
name: "node 1.2.1",
children: [{
id: 5,
name: "node 1.2.1.1",
children: []
}]
}]
}]
}])
return (
<div className="App">
<Tree data = {data} setData = {setData} margin={15}/>
<button>Add child</button>
</div>
);
}
and
const Tree = ({data, margin, setData}) => {
return (
<Fragment>
{data.map((element, index) => (
<div>
<div className="tier">
<div
style={{marginLeft: `${margin}px`}}
>
{element.name}
</div>
</div>
{element.children && element.children.length ? <Tree
data={element.children}
setData={setData}
margin={margin + 15}
/> : false}
</div>))}
</Fragment>
);
};
I need to add a node selection when clicking on it. I have an idea about the implementation: create a state in which the ID of the desired node will be stored, and design it using CSS.
But I have absolutely no idea how to add a child node to the selected node when the button is clicked.
You did half of the job and your idea about storing id is the best overall, you could actually store the selected item itself but it will be not as great as it sounds due to mutation of this item, you will not be able to trigger dependent hooks update for it.
So you only need to store the id of selected item and a few utility things to simplify the work with the tree. On the code below you can see the flatTree that was calculated with useMemo from the original data. It just flattens your tree to an array, so you will be able to easilly find real selected item and to calculate the next id that will be inserted on button click. And to add a few more props to the Tree component itself. onClick to handle actual click and selectedTreeItem just to add some classes for selected item (optional).
import "./styles.css";
import { Fragment, useEffect, useMemo, useState } from "react";
export default function App() {
const [data, setData] = useState([{id:1,name:"node 1",children:[{id:2,name:"node 1.1",children:[{id:8,name:"node 1.1.1",children:[]}]},{id:3,name:"node 1.2",children:[{id:4,name:"node 1.2.1",children:[{id:5,name:"node 1.2.1.1",children:[]}]}]}]}]);
const [selectedTreeItemId, setSelectedTreeItemId] = useState();
const flatTree = useMemo(() => {
const flat = (item) => [item, ...(item.children || []).flatMap(flat)];
return data.flatMap(flat);
}, [data]);
const selectedTreeItem = useMemo(() => {
if (!selectedTreeItemId || !flatTree) return undefined;
return flatTree.find((x) => x.id === selectedTreeItemId);
}, [flatTree, selectedTreeItemId]);
const getNextListItemId = () => {
const allIds = flatTree.map((x) => x.id);
return Math.max(...allIds) + 1;
};
const onTreeItemClick = ({ id }) => {
setSelectedTreeItemId((curr) => (curr === id ? undefined : id));
};
const onAddChildClick = () => {
if (!selectedTreeItem) return;
if (!selectedTreeItem.children) selectedTreeItem.children = [];
const newObj = {
id: getNextListItemId(),
name: `${selectedTreeItem.name}.${selectedTreeItem.children.length + 1}`,
children: []
};
selectedTreeItem.children.push(newObj);
// dirty deep-clone of the tree to force "selectedTreeItem"
// to be recalculated and its !reference! to be updated.
// so any hook that has a "selectedTreeItem" in depsArray
// will be executed now (as expected)
setData((curr) => JSON.parse(JSON.stringify(curr)));
};
useEffect(() => {
console.log("selectedTreeItem: ", selectedTreeItem);
}, [selectedTreeItem]);
return (
<div className="App">
<Tree
data={data}
margin={15}
onTreeItemClick={onTreeItemClick}
selectedTreeItem={selectedTreeItem}
/>
<button
type="button"
disabled={!selectedTreeItem}
onClick={onAddChildClick}
>
Add child
</button>
</div>
);
}
const Tree = ({ data, margin, onTreeItemClick, selectedTreeItem }) => {
return (
<Fragment>
{data.map((element) => (
<div key={element.id}>
<div
className={`tier ${
selectedTreeItem === element ? "selected-list-item" : ""
}`}
onClick={() => onTreeItemClick(element)}
>
<div style={{ marginLeft: `${margin}px` }}>
{element.id}: {element.name}
</div>
</div>
{element.children?.length > 0 && (
<Tree
data={element.children}
margin={margin + 15}
onTreeItemClick={onTreeItemClick}
selectedTreeItem={selectedTreeItem}
/>
)}
</div>
))}
</Fragment>
);
};
Nested array in your date tree you should map as well. It should look like that
{element.children?.map(el, I => (
<p key={I}> {el. name}</p>
{el.children?.map(child, ind => (
<p key={ind}>{child.name}</p>
)}
)}
the ? is very important, because it would work only if there was a date like that.

Why the React List component gets rendered twice?

I have a React code as below:
import React, { useState, useCallback } from "react";
const initialUsers = [
{
id: "1",
name: "foo",
},
{
id: "2",
name: "bar",
},
];
const List = React.memo(({ users, onRemove }) => {
console.log("rendering list");
return (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} <span onClick={() => onRemove(user.id)}>X</span>
</li>
))}
</ul>
);
});
const App = () => {
const [users, setUsers] = useState(initialUsers);
const [text, setText] = useState("");
const handleRemove = useCallback(
(userId) => {
console.log("handleRemove", userId);
const filteredUsers = users.filter((user) => user.id !== userId);
setUsers(filteredUsers);
},
[users]
);
const handleText = (event) => {
setText(event.target.value);
};
return (
<div>
<input type="text" value={text} onChange={handleText} />
<List users={users} onRemove={handleRemove} />
</div>
);
};
export default App;
When I first load the page, I am seeing the rendering list got dumped twice in the browser console, see https://codesandbox.io/s/elegant-bash-ic9uqv?file=/src/App.js
Why is that?
That's because you have used StrictMode in your index.js
When the StrictMode is used, the lifecycle methods are called twice.
It happens for detecting unexpected side effects in your lifecycle methods.
Reference: https://reactjs.org/docs/strict-mode.html

How to use this to target a item in a mapped list in react

I am still pretty new and am doing a simple food ordering app in React. I have a file of dummy meals and am able to create a cart through user input, create a modal on the "Your Cart" button from the header, and map through the cart displaying as a list, but I am unable to figure out how to delete an item from the cart. I realize I need to target the id of the list item that the X is connected to, but for some reason I can't get the this command to work.
Is there something about the this command that I am not understanding when it comes to a mapped list item? So far I have both tried this through my CartModal component and the Home component, but both turn up undefined when using this and I throw an error when using e.target (enter different options like e.target.value, e.target.id, etc...), and have even tried working on an inline targeting (which is where the code below is at currently) but still comes up nothing.
I will add a couple dummy meals to this as well.
Thanks so much!
const initialState = 0;
const reducer = (state, action) => {
switch (action) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
case "reset":
return initialState;
default:
return state;
}
};
export const CountContext = React.createContext();
const Home = (props) => {
const [cart, setCart] = useState([]);
const [count, dispatch] = useReducer(reducer, initialState);
const {isVisible, toggleModal} = useModal();
let totalPrice;
let cartCounter ;
const cartUpdaterHandler = (cartItem) =>{
setCart([...cart, cartItem]);
cartItem= cartItem;
}
useEffect(() => {
cartCounter = cart.length;
totalPrice = cart.reduce((acc, {price}) => parseFloat(acc) + parseFloat(price), 0).toFixed(2);
}, [cart])
const modalHandler = () => {
toggleModal(true)
}
const removeCI = () => {
console.log(cart)
}
return (
<CountContext.Provider
value={{ countState: count,
countDispatch: dispatch,
currentCart: cart
}}
className={classes.contextProvider}
>
{isVisible && (
<CartModal
overlayClassName="custom_overlay"
isVisible={isVisible}
hideModal={toggleModal}
currentCart={cart}
totalPrice={totalPrice}
remove={removeCI}
/>
)}
<Header cart={cart} cartCounter={cart} modal={modalHandler}/>
<Intro />
<MealForm onAdd={cartUpdaterHandler} />
{/* <Cart /> */}
{/* <CartModal isVisible={isVisible} hideModal={toggleModal} currentCart={cart} totalPrice={totalPrice}/> */}
</CountContext.Provider>
)
}
export default Home;
const CartModal = ({ isVisible, hideModal, currentCart, remove}) => {
// const checkKey = () => {
// console.log(cartItem.id)
// console.log(this.id)
// }
return isVisible
? createPortal(
<Card className={classes.modal}>
<div className={classes.modalBackground}>
<div className={classes.modalDiv}>
<div className={classes.modalHeader}>
<h5 className={classes.modalHeaderH5}>Your Cart</h5>
<button className={classes.modalHeaderX} onClick={hideModal}> X </button>
</div>
<div className={classes.modalBody}>
{currentCart.map((cartItem, i) => {
return<div key={Math.random()}>
<ul className={classes.cartModalItem} key={Math.random()}>
<div className={classes.cartModalItemInfo} >
<li className={classes.cartModalName}>{cartItem.name}</li>
<li className={classes.cartModalPrice}>{cartItem.price}</li>
<li className={classes.cartModalDesc}>{cartItem.description}</li>
</div>
//I am asking about the X below this.
<button className={classes.cartModalX} onClick={()=>{console.log(this)}}>X</button>
</ul>
</div>
})}
<h5 className={classes.modalTotal}>Total:{currentCart.reduce((acc, {price}) => parseFloat(acc) + parseFloat(price), 0).toFixed(2)} </h5>
</div>
<div className={classes.modalFooter}>
<button className={classes.closeModalButton} onClick={hideModal}> Close </button>
<button className={classes.orderModalButton}>Checkout</button>
</div>
</div>
</div>
</Card>,
document.body,
)
: null;
};
export default CartModal;
const AvalibleMeals = [
{
id: 'm1',
name: 'Sushi',
description: 'Finest fish and veggies',
price: 22.99,
},
{
id: 'm2',
name: 'Schnitzel',
description: 'A german specialty!',
price: 16.5,
},
{
id: 'm3',
name: 'Barbecue Burger',
description: 'American, raw, meaty',
price: 12.99,
},
{
id: 'm4',
name: 'Green Bowl',
description: 'Healthy...and green...',
price: 18.99,
},
];
export default AvalibleMeals;

Sending message in react.js returns id instead of message

I have a community chat page on my business website. I am using firebase firestore to make the messages realtime but whenever I hit the send button it returns an id instead of the actual message the user types in the text box.
Here is my code:
import React, { useEffect, useState } from "react";
import db from "../firebase";
import InputBar from "./InputBar";
import Message from "./Message";
import firebaseApp from "../firebase";
import "../styles/Chat.css";
const Chat = () => {
const [input, setInput] = useState("");
const [messages, setMessages] = useState([]);
useEffect(() => {
db.collection("messages").onSnapshot((snapshot) => {
setMessages(
snapshot.docs.map((doc) => ({
id: doc.id,
message: doc.data(),
}))
);
});
}, []);
const sendMessage = (event) => {
event.preventDefault();
db.collection("messages").add({
message: input,
});
setMessages([
...messages, input
]);
setInput("");
};
// setTimeout(() => {
// const chat = document.querySelector("#chat");
// chat.scroll({ behavior: "smooth" });
// chat.scrollTop = chat.scrollHeight;
// }, 500);
const scrollToMessage = () => {
const chat = document.querySelector("#messages");
chat.scroll({ behavior: "smooth" });
chat.scrollBottom = -chat.scrollHeight;
}
useEffect(() => {
scrollToMessage();
}, [messages])
return (
<div className="chat-card overflow-y-scroll shadow-md mx-auto p-5 w-10/12 h-4/6 my-24">
<h1 className="text-white text-center font-bold text-lg">
Community Chat
</h1>
<div className="my-5 mx-auto">
<form onSubmit={sendMessage}>
<input
value={input}
onChange={(event) => setInput(event.target.value)}
className="w-full py-2 rounded"
type="text"
/>
<button className="w-full text-white font-bold my-2 rounded bg-red-500 py-2">
SEND
</button>
</form>
</div>
<div id="messages" className="messages">
{
messages.map((id, message) => {
return (
<Message key={id} msg={message} />
)
})
}
</div>
</div>
);
};
export default Chat;
these are the two firebase parts:
useEffect(() => {
db.collection("messages").onSnapshot((snapshot) => {
setMessages(
snapshot.docs.map((doc) => ({
id: doc.id,
message: doc.data(),
}))
);
});
}, []);
I am guessing this is where the bug is:
const sendMessage = (event) => {
event.preventDefault();
db.collection("messages").add({
message: input,
});
setMessages([
...messages, input
]);
setInput("");
};
Any help would be awesome!
Here is what I get when I console.log(messages):
const [messages, setMessages] = useState(["hello", "goodbye"]);
useEffect(() => {
db.collection("messages").onSnapshot((snapshot) => {
console.log(messages);
setMessages(
snapshot.docs.map((doc) => ({
message: doc.data(),
id: doc.id,
}))
);
});
console.log(messages);
}, []);
result:
array(2)
array(2)
error:
Objects are not valid as a React child (found: object with keys {message, id}). If you meant to render a collection of children, use an array instead.
You have the map args reversed. It should be...
messages.map((message, id) => {
return (
<Message key={id} msg={message} />
)
})

React validation of dynamic checkboxes

I have 7 checkboxes that are rendered within a map method.. each checkbox has a question and when all checkboxes are checked, a button should be activated.. can someone please tell me how can I validate this using useState() hook.. I know how to do this with one checkbox, but I don't know how to handle multiple checkboxes that are rendered within a map method. A code example should be very helpful. Any help will be appreciated.
const [isChecked, setIsChecked] = useState(false);
{ questions.map(q => (
<input
type={q.radio ? "radio" : "checkbox"}
onClick={() => setIsChecked(!isChecked)}
value={isChecked}
id={option.id}
value={option.value}
name={`${q.name}`}
/>
There are 7 checkboxes rendered with this map method. How should I handle the state in this case?
If your questions are dynamic list you can use below approach:
const QUESTIONS = ["Do you use Stackoverflow?", "Do you asked a question?"];
function Confirmation({ data }) {
const [questions, setQuestions] = React.useState(
data.map((question, index) => {
return { id: index, text: question, checked: false };
})
);
const handleClick = (id, checked) => {
const newQuestions = [...questions];
const index = newQuestions.findIndex((q) => q.id === id);
newQuestions[index].checked = checked;
setQuestions(newQuestions);
};
const isButtonDisabled = questions.some((q) => q.checked === false);
return (
<div>
{questions.map((question) => (
<React.Fragment>
<input
key={question.id}
name={`question-${question.id}`}
type="checkbox"
checked={question.checked}
onClick={(e) => handleClick(question.id, e.target.checked)}
/>
<label htmlFor={`question-${question.id}`}>{question.text}</label>
</React.Fragment>
))}
<button disabled={isButtonDisabled}>Confirm</button>
</div>
);
}
ReactDOM.render(<Confirmation data={QUESTIONS} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
To add to thedude's answer, you can dynamically adjust the checked array based on changes to your questions array so that you don't have to manually maintain the initial states or remember to sync it to the new length of questions if it changes.
const [checked, setChecked] = useState([])
const arrLength = questions.length;
useEffect(() => {
setChecked(() => (
Array(arrLength).fill().map((_, i) => checked[i] || false)
));
}, [arrLength]);
Following your example of mapping over your questions.
You could load those questions in useState and append the check value to them.
Click the Run Code Snippet below to see it working.
const { useState, useEffect } = React;
const App = props => {
const { questions } = props;
const [state, setState] = useState(null);
const onChangeCheckbox = index => event => {
const newState = [...state];
newState[index].checked = !newState[index].checked;
setState(newState);
}
useEffect(() => {
setState(questions.map(i => ({...i, checked: false }) ));
}, [questions]);
// Not loaded yet
if (!state) return <div></div>;
return <div>
{state && state.length > 0 && <div>
{state.map((i, k) => <p key={`question-${k}`}>
<label><input onChange={onChangeCheckbox(k)} type="checkbox" name={i.name} checked={i.checked} /> {i.question}</label>
</p>)}
</div>}
<hr />
<p><small>Debug</small></p>
<pre><code>{JSON.stringify(state, null, ' ')}</code></pre>
</div>;
};
const questions = [
{
name: 'cyborg',
question: 'Are you a cyborg?'
},
{
name: 'earth',
question: 'Do you live on earth?'
},
{
name: 'alien',
question: 'Are you from another planet?'
}
];
ReactDOM.render(<App questions={questions} />, document.querySelector('#root'));
body {
font-family: Arial, sans-serif;
}
pre {
background: #efefef;
padding: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Use the key param to give each checkbox an identifier so it can be modified.
const [isChecked, setIsChecked] = useState([])
useEffect(() => {
compareList()
})
{ questions.map((q,i) => (
<input
type={q.radio ? "radio" : "checkbox"}
onClick={() => setIsChecked(!isChecked[i])}
value={isChecked[i]}
id={option.id}
value={option.value}
name={`${q.name}`}
key=i
/>
If you use controlled input the solution might be like this:
const initialState = [
{
name: "Check option 1",
checked: false
},
{
name: "Check option 2",
checked: false
},
]
const App = () => {
const [checkboxes, setCheckboxes] = useState(initialState)
const [quizFinished, setQuizFinished] = useState(false)
// the useEffect checks if all questions are completed
useEffect(()=>{
setQuizFinished(checkboxes.reduce((acc, cbx) => acc && cbx.checked, true))
}, [checkboxes])
function handleChange(e) {
setCheckboxes(
checkboxes.map(cbx => cbx.name === e.target.name ? {...cbx, checked:!cbx.checked} : cbx)
)
}
return (
<>
{checkboxes.map(cbx => <Checkbox name={cbx.name} checked={cbx.checked} onChange={handleChange}/>)}
{quizFinished && <button>Finish</button>}
</>
)
}
const Checkbox = ({name, checked, onChange}) => {
return (
<>
<label>
{name}
<input type="checkbox" name={name} checked={checked} onChange={onChange} />
</label>
<br/>
</>
)
}
You can model your state to fit your usecase. For a list of 7 checkboxes you can have an array with 7 values:
const [checked, setChecked] = useState([false, false, false, false, false, false, false])
The when a check box is clicked:
{ questions.map((q, i) => (
<input
type={q.radio ? "radio" : "checkbox"}
onClick={() => setChecked(current => {
const newState = [...current]
newState[i] = !newState[i]
return newState
} )}
value={checked[i]}
id={option.id}
value={option.value}
name={`${q.name}`}
/>
To know if all are checked you can defined a variable:
const areAllChecked = checked.every(Boolean)

Resources