I'm having an issue where React seems to be rendering a deleted array element after I've removed it from an array in the components state. After setState(), rendering is triggered, but the deleted item is shown, instead of the remaining item (see GIF video below).
Although the component is extremely simple and I've spent hours on this problem, I haven't been able to solve this issue. The strange thing is that the newState object actually contains the valid new list, but it's not rendered.
I really hope someone can help me figure this out!
import React from "react";
import Button from "#material-ui/core/Button";
import update from "immutability-helper";
import Grid from "#material-ui/core/Grid";
import TextField from "#material-ui/core/TextField";
import * as R from "ramda";
class SessionNoteGroup extends React.Component {
state = {
id: this.props.id,
notes: this.props.notes
};
render() {
const { classes } = this.props;
const { id, notes } = this.state;
return (
<div>
<Grid container>
<Grid item xs={12}>
<TextField
multiline
fullWidth
id="notes"
name="notes"
label="Notes"
rows="2"
value={notes}
onChange={this.handleValueChange}
/>
</Grid>
</Grid>
<Button variant="outlined" color="primary" onClick={this.handleDelete}>
Delete
</Button>
</div>
);
}
handleValueChange = event => {
const { name, value } = event.target;
const { id, notes } = this.state;
let newState = {
id: id,
notes: value
};
this.setState(newState);
};
handleDelete = () => {
this.props.onDelete(this.state.id);
};
}
class SessionNotes extends React.Component {
state = {
notes: this.props.notes.slice(),
deleted: []
};
next_id = 2;
createNotes = () => {
let notesList = [];
for (let i = 0; i < this.state.notes.length; i++) {
const { id, notes } = this.state.notes[i];
notesList.push(
<SessionNoteGroup
id={id}
notes={notes}
onDelete={this.handleDelete}
index={i + 1}
/>
);
}
console.log(notesList);
return notesList;
};
handleDelete = id => {
const newNotes = R.filter(note => note.id !== id, this.state.notes);
this.setState({ notes: newNotes });
};
handleClickAdd = async () => {
const note = {
id: this.next_id,
notes: ""
};
this.next_id++;
const newState = update(this.state, { notes: { $push: [note] } });
this.setState(newState);
};
render() {
return (
<div>
{this.createNotes()}
<Button
variant="outlined"
color="primary"
onClick={this.handleClickAdd}
>
Add
</Button>
</div>
);
}
}
export default SessionNotes;
Few things.
When you want to set the state, based on the prev state, use the setState with a callback, which takes as argument the prevState. So the next code:
handleValueChange = event => {
const { name, value } = event.target;
const { id, notes } = this.state;
let newState = {
id: id,
notes: value
};
this.setState(newState);
};
Will be something like:
handleValueChange = event => {
const { name, value } = event.target;
const { id, notes } = this.state;
this.setState(prevState => ({ id: prevState.id, notes: value}));
};
Same in the below component:
handleDelete = id => {
const newNotes = ;
this.setState(prevState => ({ notes: R.filter(note => note.id !== id, prevState.notes) }));
};
And so on for all the times that you update the state based on previous state value.
Then when you do create a list of elements in react, use key property:
<SessionNoteGroup
key={id}
id={id}
notes={notes}
onDelete={this.handleDelete}
index={i + 1}
/>
That's used by react for managing the render of list of items
Try adding a key to the container div of your render
return (
<div key = {this.props.id}>
<Grid container>
<Grid item xs={12}>
<TextField
multiline
fullWidth
id="notes"
name="notes"
label="Notes"
rows="2"
value={notes}
onChange={this.handleValueChange}
/>
</Grid>
</Grid>
<Button variant="outlined" color="primary" onClick={this.handleDelete}>
Delete
</Button>
</div>
);
Related
I am very new to react and javascript, but I am trying to build a simple ToDo App. It wasn't complicated until I wanted to read data from a file and to display that data on the screen. The problem is that I don't know how to create a new Todo object to pass it as parameter for addTodo function.. Thaaank you all and hope you can help me!!
I will let the code here (please see the -loadFromFile- function, there is the problematic place:
import React, { useState } from 'react';
import TodoForm from './TodoForm';
import Todo from './Todo';
import data from './data/data.json'
function TodoList() {
const [todos, setTodos] = useState([]);
const loadFromFile = data.map( ( data) => {
const newTodo = addTodo(new Todo(data.id,data.text));
return ( {newTodo} )});
const addTodo = todo => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(...todos);
};
const updateTodo = (todoId, newValue) => {
if (!newValue.text || /^\s*$/.test(newValue.text)) {
return;
}
setTodos(prev => prev.map(item => (item.id === todoId ? newValue : item)));
};
const removeTodo = id => {
const removedArr = [...todos].filter(todo => todo.id !== id);
setTodos(removedArr);
};
const completeTodo = id => {
let updatedTodos = todos.map(todo => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{loadFromFile}
<Todo
todos={todos}
completeTodo={completeTodo}
removeTodo={removeTodo}
updateTodo={updateTodo}
/>
</>
);
}
export default TodoList;
I want to create new instance of Todo object. I tried many times, many different forms, but still doesn't work. I have an id and a text from the data.json file. I want to create that instance of Todo object with these two values. But how?
import React, { useState } from 'react';
import TodoForm from './TodoForm';
import EditIcon from '#material-ui/icons/Edit';
import DeleteIcon from '#material-ui/icons/Delete';
const Todo = ({ todos, completeTodo, removeTodo, updateTodo }) => {
const [edit, setEdit] = useState({
id: null,
value: ''
});
const submitUpdate = value => {
updateTodo(edit.id, value);
setEdit({
id: null,
value: ''
});
};
if (edit.id) {
return <TodoForm edit={edit} onSubmit={submitUpdate} />;
}
return todos.map((todo, index) => (
<div
className={todo.isComplete ? 'todo-row complete' : 'todo-row'}
key={index}
>
<p> <div key={todo.id} onClick={() => completeTodo(todo.id)}>
{todo.text}
</div>
</p>
<div className='icons'>
<DeleteIcon fontSize="small"
onClick={() => removeTodo(todo.id)}
className='delete-icon'
/>
<EditIcon
onClick={() => setEdit({ id: todo.id, value: todo.text })}
className='edit-icon'
/>
</div>
</div>
));
};
export default Todo;
import React, { useState, useEffect, useRef } from 'react';
import { Fab, IconButton } from "#material-ui/core";
import AddIcon from '#material-ui/icons/Add';
function TodoForm(props) {
const [input, setInput] = useState(props.edit ? props.edit.value : '');
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
});
const handleChange = e => {
setInput(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
props.onSubmit({
id: Math.floor(Math.random() * 10000),
text: input
});
setInput('');
};
return (
<form onSubmit={handleSubmit} className='todo-form'>
{props.edit ? (
<>
<textarea cols="10"
placeholder='Update item'
value={input}
onChange={handleChange}
name='text'
ref={inputRef}
className='todo-input edit'
/>
<button onClick={handleSubmit} className='todo-button edit'>
Save
</button>
</>
) : (
<>
<input
placeholder='Add item'
value={input}
onChange={handleChange}
name='text'
className='todo-input'
ref={inputRef}
/>
<Fab color="primary" aria-label="add">
< AddIcon onClick={handleSubmit} fontSize="small" />
</Fab>
</>
)}
</form>
);
}
export default TodoForm;
Issue
Ah, I see what you are getting at now, you are wanting to load some list of todos from an external file. The main issue I see in your code is that you are attempting to call/construct a Todo React component manually and this simply isn't how React works. You render data/state/props into JSX and pass this to React and React handles instantiating the components and computing the rendered DOM.
const loadFromFile = data.map((data) => {
const newTodo = addTodo(new Todo(data.id, data.text));
return ({newTodo});
});
Todo shouldn't be invoked directly, React handles this.
Solution
Since it appears the data is already an array of objects with the id and text properties, it conveniently matches what you store in state. You can simply pass data as the initial todos state value.
const [todos, setTodos] = useState(data);
If the data wasn't readily consumable you could create an initialization function to take the data and transform/map it to the object shape your code needs.
const initializeState = () => data.map(item => ({
id: item.itemId,
text: item.dataPayload,
}));
const [todos, setTodos]= useState(initializeState);
Running Example:
import data from "./data.json";
function TodoList() {
const [todos, setTodos] = useState(data); // <-- initial state
const addTodo = (text) => {
if (!text || /^\s*$/.test(text)) {
return;
}
setTodos((todos) => [todo, ...todos]);
};
const updateTodo = (id, newTodo) => {
if (!newTodo.text || /^\s*$/.test(newTodo.text)) {
return;
}
setTodos((todos) => todos.map((todo) => (todo.id === id ? newTodo : todo)));
};
const removeTodo = (id) => {
setTodos((todos) => todos.filter((todo) => todo.id !== id));
};
const completeTodo = (id) => {
setTodos((todos) =>
todos.map((todo) =>
todo.id === id
? {
...todo,
isComplete: !todo.isComplete
}
: todo
)
);
};
return (
<>
<TodoForm onSubmit={addTodo} />
<Todo
todos={todos}
completeTodo={completeTodo}
removeTodo={removeTodo}
updateTodo={updateTodo}
/>
</>
);
}
The reducer function works fine except that previously added object element to the item array property gets over written whenever new object element is added.
For example, if state.item contains {number: 1} and I add {number: 2}, it becomes [{number: 2},{number: 2}] instead of [{number: 1},{number: 2}].
The reducer function:
const reducer = (state, action) => {
if (action.type === "ADD") {
let newItem = state.item.concat(action.addItem);
console.log("action.addItem:", action.addItem);
console.log("state.item:", state.item);
return { item: newItem };
}
};
Is there any solution this problem?
Thank You.
Parent Component:
import React from "react";
import { useReducer } from "react";
import { CreateContext } from "./CreateContext";
const initialState = {
item: [],
};
const reducer = (state, action) => {
if (action.type === "ADD") {
let newItem = state.item.concat(action.addItem);
console.log("action.addItem:", action.addItem);
console.log("state.item:", state.item);
return { item: newItem };
}
};
const AuthProvider = (props) => {
// define useReducer
const [state, dispatch] = useReducer(reducer, initialState);
// define handlers
const addItemHandler = (addItem) => {
console.log("addItemHandler");
dispatch({ type: "ADD", addItem: addItem });
};
const data = {
addItem: addItemHandler,
number: 0,
item: state.item,
};
return (
<CreateContext.Provider value={data}>
{props.children}
</CreateContext.Provider>
);
};
export default AuthProvider;
Child component:
import React, { useState, useContext } from "react";
import {
Button,
Card,
CardActionArea,
CardActions,
CardContent,
CardMedia,
makeStyles,
Typography,
Collapse,
TextField,
IconButton,
} from "#material-ui/core";
import clsx from "clsx";
import AddBoxIcon from "#material-ui/icons/AddBox";
import { Grid } from "#material-ui/core";
import { CreateContext } from "../Store/CreateContext";
const useStyles = makeStyles((theme) => ({
card: {
marginBottom: theme.spacing(5),
},
media: {
height: 250,
// smaller image for mobile
[theme.breakpoints.down("sm")]: {
height: 150,
},
},
priceDetail: {
marginLeft: theme.spacing(15),
},
numberTextField: {
width: 52,
},
addBtn: {
fontSize: 60,
},
}));
const data = {
id: null,
name: null,
price: null,
quantity: null,
};
// img and title from the feed component
const Food = ({ img, title, description, price, id }) => {
const classes = useStyles();
// expand the description
const [expanded, setExpanded] = React.useState(false);
const handleExpandClick = () => {
setExpanded(!expanded);
};
const newPrice = `RM${price.toFixed(2)} `;
////// process the form //////
//get quantity from TextField
const [quantity, setQuantity] = useState("");
const quantityHandler = (enteredQuantity) => {
console.log("enteredQuantity:", enteredQuantity);
setQuantity(enteredQuantity.target.value);
};
// use useContext
const AuthData = useContext(CreateContext);
const submitHandler = (e) => {
console.log("submit is pressed");
e.preventDefault();
data.id = id;
data.title = title;
console.log("data.id:", data.id);
data.price = price;
console.log("data.price:", data.price);
data.quantity = quantity;
console.log("quantity:", quantity);
AuthData.addItem(data);
console.log("AuthData:", AuthData.number);
};
return (
<Grid item xs={12} md={6}>
<form onSubmit={submitHandler}>
<Card className={classes.card} id={id}>
<CardActionArea>
<CardMedia className={classes.media} image={img} title="My Card" />
<CardContent>
<Typography gutterBottom variant="h5">
{title}
</Typography>
<Button
size="small"
color="primary"
className={clsx(classes.expand, {
[classes.expandOpen]: expanded,
})}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
>
Learn More
</Button>
<CardActionArea>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
<Typography paragraph>{description}</Typography>
</CardContent>
</Collapse>
</CardActionArea>
</CardContent>
</CardActionArea>
<CardActions>
{" "}
<Typography variant="h6" className={classes.priceDetail}>
{newPrice}
</Typography>
<Typography variant="h6" className={""}>
x
</Typography>
<TextField
id={id}
label="amount"
type="number"
// value={}
// onChange={}
className={classes.numberTextField}
label=""
variant="outlined"
min="1"
max="5"
step="1"
defaultValue="0"
size="small"
onChange={quantityHandler}
input={id}
// ref={quantity}
/>
<IconButton aria-label="" onClick={""} type="submit">
<AddBoxIcon
color="secondary"
className={classes.addBtn}
></AddBoxIcon>
</IconButton>
</CardActions>
</Card>
</form>
</Grid>
);
};
export default Food;
The problem is entirely in your child component, and nothing to do with your reducer. It's how you are passing in the data that becomes the addItem payload in the action you dispatch.
I have reproduced below the relevant parts of the child component (or rather the whole module), so you can see the problem more clearly:
const data = {
id: null,
name: null,
price: null,
quantity: null,
};
const Food = ({ img, title, description, price, id }) => {
// more code that isn't relevant here
const submitHandler = (e) => {
console.log("submit is pressed");
e.preventDefault();
data.id = id;
data.title = title;
console.log("data.id:", data.id);
data.price = price;
console.log("data.price:", data.price);
data.quantity = quantity;
console.log("quantity:", quantity);
AuthData.addItem(data);
console.log("AuthData:", AuthData.number);
};
// more code that isn't relevant here
}
The data that you are passing to AuthData.addItem (that ends up being passed to the reducer) isn't a new object each time - it's a single "global" (module-level) constant that you simply mutate each time you use it. This is what's causing your state to mutate - because each object that ends up in the array of your state.items is a reference to that same object, so each mutation you make (inside submitHandler) ends up changing every copy!
You can easily fix this in the child component, by not simply mutating the same object each time but recreating a new one. I don't see any reason for what you are doing, so simply stop doing it! I would rewrite it as below:
// note NO const data = ...
const Food = ({ img, title, description, price, id }) => {
// more code that isn't relevant here
const submitHandler = (e) => {
console.log("submit is pressed");
e.preventDefault();
const data = {};
data.id = id;
data.title = title;
console.log("data.id:", data.id);
data.price = price;
console.log("data.price:", data.price);
data.quantity = quantity;
console.log("quantity:", quantity);
AuthData.addItem(data);
console.log("AuthData:", AuthData.number);
};
// more code that isn't relevant here
}
Doing it this way, each object in the state "item" array will be unique, and you can dispatch new ones without affecting the old ones!
I am trying to introduce unique identifiers for list items instead of using the index but every method I try, I can't seem to get it working in the child. This is the base I am working with. I did install and imported import { v4 as uuidv4 } from 'uuid'; to make it a bit easier
All you have to do is simply put in 'uuidv4()' to generate a random ID
Parent
import React from 'react';
import './App.css';
import ShoppingCartList from './ShoppingCartList'
import { v4 as uuidv4 } from 'uuid';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
shoppingCart: [],
newItem: '',
errorMessage: 'false',
};
this.onRemoveItem = this.onRemoveItem.bind(this);
}
handleChange = (e) => {
this.setState ({ newItem: e.target.value})
}
handleClickAdd = (e) => {
if(this.state.newItem === '') {
this.setState({errorMessage: 'true'});
} else {
return ( this.setState({ shoppingCart: this.state.shoppingCart.concat(this.state.newItem) }),
this.setState({newItem: ''}),
this.setState({errorMessage: 'false'})
)}
}
handleSubmit = (e) => {
e.preventDefault()
}
onRemoveItem = (i) => {
this.setState(state => {
const shoppingCart = state.shoppingCart.filter((item, j) => i !== j);
return {shoppingCart}
})}
render() {
return (
<div>
<form onSubmit ={this.handleSubmit}>
Shopping Cart Items
<br></br>
{ this.state.errorMessage === 'true' &&
<p className='error'> Please enter an item </p> }
<ul>
{this.state.shoppingCart.map((item, index,) => {
return <ShoppingCartList
item={item}
index={index}
onRemoveItem={this.onRemoveItem}
/>
})}
</ul>
<input
placeholder='Enter your item here'
value={this.state.newItem}
onChange={this.handleChange}
></input>
<button type='submit' onClick={this.handleClickAdd}>Add to Shopping list</button>
</form>
</div>
)
}
}
export default App;
Child
[code]
import React from 'react';
function ShoppingCartList ({item,index, onRemoveItem}) {
return (
<li key={item}>{item} <button type="button" onClick={() => onRemoveItem(index)}>Delete</button></li>
)
}
export default ShoppingCartList;
Issues
React keys should be defined on the element/component being mapped, inside the child component is the wrong location
Solution
When adding items to the shopping cart, generate the unique id when adding to state.
Use the item id as the react key in the parent when mapping the cart
items, and as a way to identify the item to be removed from the cart.
Update handleClickAdd to create a new item object with id and value. Spread the existing cart array into a new array and append the new item object to the end.
handleClickAdd = (e) => {
if (this.state.newItem === "") {
this.setState({ errorMessage: true });
} else {
this.setState((prevState) => ({
shoppingCart: [
...prevState.shoppingCart,
{
id: uuidv4(), // <-- new unique id
value: prevState.newItem // <-- item value
}
],
newItem: "",
errorMessage: false
}));
}
};
Update onRemoveItem to take an id to filter by.
onRemoveItem = (id) => {
this.setState((prevState) => ({
shoppingCart: prevState.shoppingCart.filter((item) => item.id !== id)
}));
};
Update your render to add the react key to ShoppingCartList.
{this.state.shoppingCart.map((item) => {
return (
<ShoppingCartList
item={item}
key={item.id}
onRemoveItem={this.onRemoveItem}
/>
);
})}
Update ShoppingCartList to render the item value and pass the item id to the remove item callback.
const ShoppingCartList = ({ item, onRemoveItem }) => (
<li>
{item.value}{" "}
<button type="button" onClick={() => onRemoveItem(item.id)}>
Delete
</button>
</li>
);
I have created a list using material UI and reactjs, and when a new element is added to the list the new element will go on top of the list.
I have a requirement where when I click on an element on the list the element should be strike-through and that now that element should be listed on the bottom of the list.
I was able to strike-through the element when clicked, but I am confused as to how to bring the element to the bottom of the list
How should I approach this problem?
The code of the listlayout.js is presented here, In this code, the added items are listed, and I need to find the way to change the list order when an element is stricked
app.js
class App extends Component {
constructor(props) {
super(props);
this.state={
items:[],
newItem:{
id:'',
itemText:''
},
updateItem:false
};
this.handleInput = this.handleInput.bind(this);
this.addItem = this.addItem.bind(this);
}
handleInput = e =>{
this.setState({
newItem:{
id:1 + Math.random(),
itemText: e.target.value
}
});
};
addItem = e =>{
e.preventDefault();
const typedItem = this.state.newItem;
if(typedItem.itemText !==""){
const typedItems=[...this.state.items,typedItem];
this.setState({
items:typedItems,
newItem:{
id:'',
itemText: ''
},
updateItem:false
})
}
};
render() {
return (
<div >
<HeaderBar/>
<ListLayout items={this.state.items}
/>
</div>
);
}
}
export default App;
ListLayout.js
const ToDoList = props => {
const clearList = props.clearList;
const deleteItem = props.deleteItem;
const updateItem = props.updateItem;
const strikeList = props.strikeList;
const listItems = item => {
return <div key={item.id}>{item.itemText}</div>;
};
const completed = id => {
document.getElementById(id).style.textDecoration = "line-through";
return true;
};
const strikeTextMethod = id => {
completed(id);
};
return (
<div>
<Grid container justify="center" alignContent="center">
<Grid item xs={12} md={6}>
<Typography variant="h6" className={classes.title}>
To do List
</Typography>
<div className={classes.demo}>
<List dense={dense}>
{items
.slice(0)
.reverse()
.map(x => (
<ListItem
key={x.id}
button
id={x.id}
onClick={() => strikeTextMethod(x.id)}
divider
>
<ListItemText primary={listItems(x)} />
<ListItemSecondaryAction></ListItemSecondaryAction>
</ListItem>
))}
</List>
</div>
</Grid>
</Grid>
<br />
</div>
);
};
export default ToDoList;
You have to mainatain the strike event for each item in the array. You can add an additional property to the array items, like isStriked or status.. something like that.
Then you can sort them accordingly..
Your code doesn't seem to be the entire solution. I don't see the definition of items as an example.
but something like this could be a workaround.
const ToDoList = props => {
const [items, setItems] = React.useState(props.items || []); // Initial values
// Maybe you need to these lines to sync the items state.
React.useEffect(
() => {
setItems(items)
},
[props.items]
)
const completed = id => {
document.getElementById(id).style.textDecoration = "line-through";
return true;
};
const strikeTextMethod = id => {
const index = items.findIndex(x => x.id === id);
const newItems = [items[index], ...items.slice(0, index - 1), ...items.slice(index + 1)]
setItems(newItems);
completed(id);
};
return (
)
}
I've managed to get my input component to render onto the dom, however I'm having a bit of trouble accessing the props.
Functional input component
const InputField = props => {
const { inputValue, updateInputValue } = props
return (
<Input>
<input
type="text"
placeholder="Please specify"
value={inputValue}
onChange={updateInputValue}
/>
<hr />
<label>Other</label>
</Input>
)
}
The component is only rendered to the dom based on an object property inside of an array
const MultiChoiceQuestions = props => {
const { multiChoiceArray, handleClick } = props
return (
<ButtonContainer>
{multiChoiceArray.map(questionChoice => {
if (questionChoice.type === 'input') {
return <InputField />
}
return (
<Button type="button" key={questionChoice.id} onClick={() => handleClick(questionChoice)}>
{questionChoice.text}
</Button>
)
})}
</ButtonContainer>
)
}
The multiChoice component is imported once again to create a top-level component that the app consumes
const Question = props => {
let responses
switch (props.data.type) {
case 'multiChoice':
responses = (
<MultiChoiceQuestions
multiChoiceArray={props.data.choices}
handleClick={props.handleClick}
inputValue={props.inputValue}
updateInputValue={props.updateInputValue}
/>
)
break
default:
responses = <div>Error: no question type: `{props.data.type}`</div>
}
const { data } = props
return (
<AnimatedDiv key={data.id}>
<QuestionText>{data.text}</QuestionText>
{responses}
</AnimatedDiv>
)
}
And the final component looks like this
class Survey extends Component {
constructor(props) {
super(props)
this.state = {
currentQuestionId: 1,
userAnswers: [],
isActive: false,
inputValue: '',
}
this.selectAnswer = this.selectAnswer.bind(this)
this.test = this.test.bind(this)
}
selectAnswer = answer => {
this.setState(state => ({
currentQuestionId: state.currentQuestionId + 1,
userAnswers: state.userAnswers.concat([answer]),
isActive: !state.isActive,
}))
}
checkInput = event => {
this.setState({
inputValue: event.target.value,
})
}
test = event => {
console.log(event.target.value)
}
render() {
const { currentQuestionId, isActive, inputValue } = this.state
const { questions } = this.props
const currentPercentage = (currentQuestionId * 100) / questions.length
return (
<SurveyContainer>
<Question
data={questions.find(q => q.id === currentQuestionId)}
className={isActive ? 'active' : ''}
handleClick={this.selectAnswer}
value={inputValue}
onChange={this.test}
/>
</SurveyContainer>
)
}
}
The InputField component renders out just fine, however, the function for my onChange event is not firing...There's a mistake somewhere in the pipeline, probably inside the question component?
It looks like you haven't passed any props to <InputField /> in your MultiChoiceQuestions component.
I can not see where you pass props from
<MultiChoiceQuestions>
...
<InputFiled props={props} />
...
</MultiChoiceQuestions>
Probably pass only the props which are needed in InputField component, such as inputValue, updateInputValue:
<InputFiled
inputValue={inputValue}
updateInputValue={updateInputValue}
/>
const InputField = (inputValue, updateInputValue) => {
...
<input
type="text"
placeholder="Please specify"
value={inputValue}
onChange={(e) => updateInputValue(e)}
/>
...
}
Hope that will help.