I can't seem to understand how to pass a string with multiple classNames as a prop using css modules? I have different classes i use everywhere for the button, and so i have on class for when it's active, and another when it's inactive. I also have different color schemes i pass.
Button:
import styles from "./Button.module.css";
const Button = (props) => {
return (
<button className={`${styles.standardButton} ${styles[props.className]}`}>
<h1>{props.text}</h1>
</button>
);
};
export default Button;
And here is the page which uses the button. The button function is at the bottom, and some input fields needs to be filled out in order for it to be "active".
import React, { useState } from "react";
import Input from "../input/Input";
import Select from "../input/Select";
import Button from "../input/Button";
const CreateJobPage1 = (props) => {
const [enteredName, setEnteredName] = useState("");
const [enteredCompany, setEnteredCompany] = useState("");
const [enteredLocation, setEnteredLocation] = useState("");
const [enteredText, setEnteredText] = useState("");
const [projectType, setProjectType] = useState('DEFAULT');
const projectTypes = ["shortfilm", "fiction", "commercial", "arthouse", "animation"];
const nameChangeHandler = (props) => {
setEnteredName(props);
};
const companyChangeHandler = (props) => {
setEnteredCompany(props);
};
const locationChangeHandler = (props) => {
setEnteredLocation(props);
};
const textChangeHandler = (props) => {
setEnteredText(props);
};
const projectTypeHandler = (props) => {
setProjectType(props);
};
return (
<div>
<Input
placeholder="What's the project name?"
enteredValue={enteredName}
onChange={nameChangeHandler}
/>
<Input
placeholder="What's the production company?"
enteredValue={enteredCompany}
onChange={companyChangeHandler}
/>
<Input
placeholder="Where's the project located?"
enteredValue={enteredLocation}
onChange={locationChangeHandler}
/>
<Select
placeholder="Choose project type"
options={projectTypes}
value={projectType}
onChangeOption={projectTypeHandler}
/>
<Input
placeholder="What's the project about?"
enteredValue={enteredText}
onChange={textChangeHandler}
formulaType="textarea"
/>
<Button
className={enteredName === "" || enteredCompany === "" || enteredLocation === "" || projectType === "DEFAULT" ? ["isRed", "formButton", "inActive"] : ["isRed", "formButton"]}
text={"Add Functions"}
/>
</div>
);
};
export default CreateJobPage1;
const classNameTest = ['isRed', 'fontColor']
<div className={`${styles.cardWrapper} ${classNameTest.map((item) => {
return styles[item] + ' '
})}`}
>
As I mentioned above in the comment, all the styles in xxx.module.scss is mean to prevent different style files have duplicate className and they will effect each other globally. when you do styles.isRed the REAL class name will likely to be src-pages-YourFileName-module__isRed--SOMEUNIQUECODE
when you try to pass them as props, u still need to access them by import styles
when you do a map return u need to have space between each return so at the end html will know they are different class
I still think is better to pass the status like isActive to control the component style. otherwise this component is not really fit the idea of a component
Related
I am currently making a Trello Clone. It has been going well so far and I've had a lot of help from everyone here, so thank you!
My current issue is that I am trying to pass the state of modalData in App.js to <ModifyModal />.
I have tried researching and Googling, and even re-writing functions and creating new ones. However, nothing had worked. I know that the state is being updated with the correct text since I made the title from Trello Clone! to {modalData} and it worked. I want the data of modalData to be passed from App.js to <ModifyModal />.
Edit: Made a functional component and it is still showing undefined for the data.
App.js:
import React, { Component } from 'react';
import './App.css';
import Todobox from './Todobox';
import ModifyModal from './ModifyModal';
import Item from './Item';
const Widget = ({parentCallback2}) => <Todobox parentCallback2={parentCallback2}/>
const Widget2 = () => <ModifyModal />
class App extends Component {
constructor(props){
super(props);
this.handleCallback = this.handleCallback.bind(this);
this.state={
elements: [],
modal: [],
modalData: null
}
}
// Creates new element box
handleNewElement = () => {
const newElement = [...this.state.elements, Widget];
this.setState({
elements: newElement
});
}
handleCallback = (itemWidget, itemData) =>{
const newModal = [...this.state.modal, itemWidget];
const newData = itemData;
this.setState({
modal: newModal,
modalData: newData
});
}
render() {
const { elements, modal, modalData } = this.state;
return (
<>
<div className='page-container'>
<div className='header'>
<a className='header-title'>{modalData}</a>
<a className='header-button' onClick={this.handleNewElement.bind(this)}>Create a list</a>
</div>
<div className='element-field'>
{elements.length !== 0 &&
elements.map((Widget, i) => <Widget key={i} parentCallback2={this.handleCallback}/>)}
</div>
</div>
{modal.length !== 0 &&
modal.map((Widget2, i) => <Widget2 key={i} itemDataToChild={modalData} />)}
</>
);
}
}
export default App;
ModifyModal.jsx:
import React from "react";
import { useState } from "react";
import trash from './trash_can.png';
import './App.css'
function ModifyModal({ itemDataToChild }){
const [hideModal, setHideModal] = useState(false);
const [content, setContent] = useState(itemDataToChild);
const handleCancel = () =>{
setHideModal(true);
}
return(
<>
<div className={`modify-modal-container ${hideModal ? 'modify-modal-container-hide' : ''}`}>
<div className='modify-modal'>
<a className='modify-title'>{content}</a>
<textarea className='modify-input' />
<div className='modify-buttons'>
<a className='modify-btn' id='modify-update-btn'>Update</a>
<a className='modify-btn' id='modify-cancel-btn' onClick={handleCancel}>Cancel</a>
<img src={trash} id='modify-delete'/>
</div>
</div>
</div>
</>
)
}
export default ModifyModal;
Any help is appreciated since I am new to this. :)
The problem is when you declared and initialized Widget2.
const Widget2 = () => <ModifyModal />
What is actually happening under the hood is that Widget2 received a function which returns a JSX.Element, it didn't actually become ModifyModal, the functional component.If you look at the line above is actually doing right.
const Widget = ({parentCallback2}) => <Todobox parentCallback2={parentCallback2}/>
There is 2 solution for this.
you can do just as Widget.
const Widget2 = ({itemDataToChild}) => <ModifyModal itemDataToChild={itemDataToChild}/>
Which I think should be the best approach since you can just rename your imports if was exported as default, and deleting the line const Widget2 = () => <ModifyModal />
import Widget2 from './ModifyModal';
Keeping in mind that the second approach would result error if used for Named Exports. Imports Reference.
For broad your understanding of JSX element and functional component I recommend take a look at their official documentation.
JSX, Components and Props
my application works fine until i refresh or re-render the page. There is multiple possible explanations for this, but I cant seem to work it out. The first problem might be in the constructor, as I am not sure if I'm allowed to do it this way. However I need to use the match.params to get the right product to use on the page.
The error:
index.js:1 Warning: Encountered two children with the same key, `Size-[object Object]`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
at select
at VariantSelector (http://localhost:3000/static/js/main.chunk.js:7811:1)
at div
at ProductPage (http://localhost:3000/static/js/main.chunk.js:6069:5)
at C (http://localhost:3000/static/js/0.chunk.js:77932:37)
at ConnectFunction (http://localhost:3000/static/js/0.chunk.js:74833:75)
at Route (http://localhost:3000/static/js/0.chunk.js:77675:29)
at Switch (http://localhost:3000/static/js/0.chunk.js:77877:29)
at div
at CollectionPage (http://localhost:3000/static/js/main.chunk.js:9200:3)
at ConnectFunction (http://localhost:3000/static/js/0.chunk.js:74833:75)
at Spinner (http://localhost:3000/static/js/main.chunk.js:7939:5)
at Route (http://localhost:3000/static/js/0.chunk.js:77675:29)
at Switch (http://localhost:3000/static/js/0.chunk.js:77877:29)
at div
at ShopPage (http://localhost:3000/static/js/main.chunk.js:10106:1)
at ConnectFunction (http://localhost:3000/static/js/0.chunk.js:74833:75)
at Route (http://localhost:3000/static/js/0.chunk.js:77675:29)
at Switch (http://localhost:3000/static/js/0.chunk.js:77877:29)
at div
at App (http://localhost:3000/static/js/main.chunk.js:803:5)
at ConnectFunction (http://localhost:3000/static/js/0.chunk.js:74833:75)
at Elements (http://localhost:3000/static/js/0.chunk.js:38972:30)
at PersistGate (http://localhost:3000/static/js/0.chunk.js:83105:5)
at Router (http://localhost:3000/static/js/0.chunk.js:77310:30)
at BrowserRouter (http://localhost:3000/static/js/0.chunk.js:76930:35)
at Provider (http://localhost:3000/static/js/0.chunk.js:74546:20)
And on the front-end:
Before refresh:
After refresh:
The error when I try to select an option after the refresh:
The code of the product page:
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { createStructuredSelector } from 'reselect';
import CustomButton from '../custom-button/custom-button.component';
import './product-detail.styles.scss';
import {addCollectionId} from '../../redux/cart/cart.utils';
import VariantSelector from '../variant-selector/VariantSelector';
import { selectCheckout, selectCart } from '../../redux/cart/cart.selectors';
import { client } from '../../shopify/shopify.utils';
// constants
const ONE_SIZE_FITS_MOST = "One Size Fits Most";
class ProductPage extends Component {
constructor(props) {
super(props);
const { match, collection} = this.props;
const { productId } = match.params;
const product = collection.products.find((product) => (product.title.toLowerCase() === productId.toLowerCase()));
let defaultOptionValues = {};
product.options.forEach((selector) => {
defaultOptionValues[selector.name] = selector.values[0].value;
});
this.state = {
selectedOptions: defaultOptionValues,
product: product,
};
this.handleOptionChange = this.handleOptionChange.bind(this);
this.handleQuantityChange = this.handleQuantityChange.bind(this);
this.findImage = this.findImage.bind(this);
}
findImage(images, variantId) {
const primary = images[0];
const image = images.filter(function (image) {
return image.variant_ids.includes(variantId);
})[0];
return (image || primary).src;
}
handleOptionChange(event) {
const target = event.target
let selectedOptions = this.state.selectedOptions;
selectedOptions[target.name] = target.value;
//console.log('selectedVariant', selectedOptions);
const selectedVariant = client.product.helpers.variantForOptions(this.state.product, selectedOptions)
//console.log(selectedVariant);
this.setState({
selectedVariant: selectedVariant,
selectedVariantImage: selectedVariant.attrs.image
});
console.log(this.state)
}
handleQuantityChange(event) {
this.setState({
selectedVariantQuantity: event.target.value
});
}
addVariantToCart(variantId, quantity) {
const lineItemsToAdd = [{variantId, quantity: parseInt(quantity, 10)}]
const checkoutId = this.props.checkout.id
client.checkout.addLineItems(checkoutId, lineItemsToAdd).then(res => {
this.props.dispatch({type: 'ADD_VARIANT_TO_CART', payload: {isCartOpen: true, checkout: res}});
});
}
render() {
let aOptionNames = [];
let variantImage = this.state.selectedVariantImage || this.state.product.images[0]
let variant = this.state.selectedVariant || this.state.product.variants[0]
let variantQuantity = this.state.selectedVariantQuantity || 1
let variantSelectors = this.state.product.options.map((option) => {
aOptionNames.push(option.name);
return (
<VariantSelector
handleOptionChange={this.handleOptionChange}
key={option.id.toString()}
option={option}
/>
);
});
let bShowOneSizeFitsMost = (variantSelectors.length === 1 && aOptionNames[0] === "Title");
return (
<div className="Product">
{this.state.product.images.length ? <img src={variantImage.src} alt={`${this.state.product.title} product shot`}/> : null}
<h5 className="Product__title">{this.state.product.title}</h5>
<p>€{variant.price}</p>
{bShowOneSizeFitsMost ? <h5 className="Product__title">{ONE_SIZE_FITS_MOST}</h5> : variantSelectors}
<label className="Product__option">
Quantity: <input className="form-control" min="1" type="number" defaultValue={variantQuantity} onChange={this.handleQuantityChange}></input>
</label>
<button className="Product__buy button" onClick={() => this.addVariantToCart(variant.id, variantQuantity)}>Add to Cart</button>
</div>
);
}
}
const mapStateToProps = createStructuredSelector({
checkout: selectCheckout
});
export default connect(
mapStateToProps,
)(withRouter(ProductPage));
The code of the variantselector:
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
import React, {Component} from 'react';
class VariantSelector extends Component {
render() {
return (
<select
className="Product__option"
name={this.props.option.name}
key={this.props.option.name}
onChange={this.props.handleOptionChange}
>
{this.props.option.values.map((value) => {
return (
<option value={value} key={`${this.props.option.name}-${value}`}>{`${value}`}</option>
)
})}
</select>
);
}
}
export default VariantSelector;
If I change the content (in the variant selector) of the option from
<option value={value} key={${this.props.option.name}-${value}}>{${value}}</option>
to
<option value={value} key={${this.props.option.name}-${value}}>{${value.value}}</option>,
it solves the issue (visually) in the option selector, but it still errors out when I try to select an other option. So I am thinking it has something to do with passing entire objects here. However, I can't find a way to make it work. Is the problem in the constructor function of the product page or in the combination of the VariantSelector and the handleOptionChange function?
If anyone has seen a similar problem, or knows what is causing it, I would love to hear. Thank you in advance!
I have a simple for with some fields in it, the fields being child components to that form. Each field validates its own value, and if it changes it should report back to the parent, which causes the field to re-render and lose focus. I want a behavior in which the child components do not update. Here's my code:
Parent (form):
function Form() {
const [validFields, setValidFields] = useState({});
const validateField = (field, isValid) => {
setValidFields(prevValidFields => ({ ...prevValidFields, [field]: isValid }))
}
const handleSubmit = (event) => {
event.preventDefault();
//will do something if all fields are valid
return false;
}
return (
<div>
<Title />
<StyledForm onSubmit={handleSubmit}>
<InputField name="fooField" reportState={validateField} isValidCondition={fooRegex} />
<Button type="submit" content="Enviar" maxWidth="none" />
</StyledForm>
</div>
);
}
export default Form;
Child (field):
function InputField(props) {
const [isValid, setValid] = useState(true);
const [content, setContent] = useState("");
const StyledInput = isValid ? Input : ErrorInput;
const validate = (event) => {
setContent(event.target.value);
setValid(stringValidator.validateField(event.target.value, props.isValidCondition))
props.reportState(props.name, isValid);
}
return (
<Field>
<Label htmlFor={props.name}>{props.name + ":"}</Label>
<StyledInput
key={"form-input-field"}
value={content}
name={props.name}
onChange={validate}>
</StyledInput>
</Field>
);
}
export default InputField;
By setting a key for my child element I was able to prevent it to lose focus when content changed. I guess I want to implement the shouldComponentUpdate as stated in React documentation, and I tried to implement it by doing the following:
Attempt 1: surround child with React.memo
const InputField = React.memo((props) {
//didn't change component content
})
export { InputField };
Attempt 2: intanciate child with useMemo on parent
const fooField = useMemo(<InputField name="fooField" reportState={validateField} isValidCondition={fooRegex} />, [fooRegex]);
return (
<div>
<Title />
<StyledForm onSubmit={handleSubmit}>
{fooField}
<Button type="submit" content="Enviar" maxWidth="none" />
</StyledForm>
</div>
);
Both didn't work. How can I make it so that when the child component isValid state changes, it doesn't re-render?
The problem is not that the component is re-rendering, it is that the component is unmounting given by this line:
const StyledInput = isValid ? Input : ErrorInput;
When react unmounts a component, react-dom will destroy the subtree for that component which is why the input is losing focus.
The correct fix is to always render the same component. What that means to you is based on how your code is structured, but I would hazard a guess that the code would end up looking a bit more like this:
function InputField(props) {
const [isValid, setValid] = useState(true);
const [content, setContent] = useState("");
const validate = (event) => {
setContent(event.target.value);
setValid(stringValidator.validateField(event.target.value, props.isValidCondition))
props.reportState(props.name, isValid);
}
return (
<Field>
<Label htmlFor={props.name}>{props.name + ":"}</Label>
<Input
valid={isValid} <-- always render an Input, and do the work of displaying error messages/styling based on the `valid` prop passed to it
value={content}
name={props.name}
onChange={validate}>
</Input>
</Field>
);
}
The canonical solution to avoiding rerendering with function components is React.useMemo:
const InputField = React.memo(function (props) {
// as above
})
However, because validateField is one of the props passed to the child component, you need to make sure it doesn't change between parent renders. Use useCallback to do that:
const validateField = useCallback((field, isValid) => {
setValidFields(prevValidFields => ({ ...prevValidFields, [field]: isValid }))
}, []);
Your useMemo solution should also work, but you need to wrap the computation in a function (see the documentation):
const fooField = useMemo(() => <InputField name="fooField" reportState={validateField} isValidCondition={fooRegex} />, [fooRegex]);
First of all, thank you in advance for your help. While making Todo App, I made adding and removing operations into functional components, but I could not make other components. I would be glad if you could help.
TodoItem.js: (I tried a lot but could not make it functional due to errors.)
class TodoItem extends Component {
render() {
const { id, title } = this.props.todo // I did not understand here what it does.
return (
<div>
<p>
<input
type="checkbox"
onChange={this.props.markComplete.bind(this, id)} // and here too
/>
{""} {title}{" "}
<button onClick={this.props.deleteTodo.bind(this, id)}>X </button>{" "}
</p>{" "}
</div>
)
}
}
Addtodo.js: (I converted it to functional but it doesn't list the input I wrote.)
const Addtodo = () => {
const [title, setTitle] = useState("")
const onSubmit = (e) => { // I made a mistake here, I don't know why there is a problem.
e.preventDefault()
setTitle("")
}
const onChange = (e) => setTitle(e.target.value)
return (
<form onSubmit={onSubmit}>
<input
type="text"
onChange={onChange}
value={title}
placeholder="Add todo"
/>
<input type="Submit" value="Submit" className="btn" />
</form>
)
}
App.js component: (I was able to make them functional. I would be glad if you can check it.)
const App = () => {
const [todos, setTodos] = useState([])
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/todos")
.then((res) => setTodos(res.data))
}, [])
const markComplete = (id) => {
setTodos(
todos.map((todo) => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
)
}
const deleteTodo = (id) => {
axios
.delete(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then((res) => setTodos([...todos.filter((todo) => todo.id !== id)]))
}
// Add Todo
const Addtodo = (title) => {
axios
.post("https://jsonplaceholder.typicode.com/todos", {
title,
completed: false
})
.then((res) => setTodos([...todos, res.data]))
}
return (
<div className="App">
<div className="container">
<Header />
<AddTodo Addtodo={Addtodo} /> // I think I made a mistake here with the props.
<Todo
todos={todos}
markComplete={markComplete}
deleteTodo={deleteTodo}
/>{" "}
</div>{" "}
</div>
)
}
There is a mismatch between your definition of the AddTodo component, and then adding your AddTodo component to the DOM. In Addtodo.js, you've got the function signature as:
const Addtodo = () => {
Therefore the Addtodo component is not expecting any props to be passed into it.
In App.js, you try to add the component to the DOM with:
<AddTodo Addtodo={Addtodo} />
So you're asking the component to be rendered with a prop called Addtodo, but as stated earlier, in the component's definition, it's not expecting to receive any props.
You need to decide whether or not you want / need the AddTodo component to receive props.
If you want it to receive the AddTodo prop, you can change the function definition to:
const Addtodo = ({ AddTodo }) => {
Also, make sure that when you export the Addtodo component, you export it as a default export, as the casing is currently inconsistent in your code (defined as Addtodo, but tried to render as AddTodo in App.js). If this is a default export though, it doesn't matter too much. Make sure your import statement for the AddTodo is the same as when you render AddTodo.
To be explicit, make sure you have export default Addtodo in Addtodo.js
Make sure the import statement for the add todo component is the same. So, if the top of App.js says import AddTodo from './Addtodo', then when you render the component later in the file, it is done like <AddTodo />. And if the import was import Addtodo from './Addtodo', then the component is rendered as <Addtodo> (casing must be consistent)
I apologise if that wasn't the clearest explanation, I'm unsure of what the actual terms for some of the things I referred to are, but I hope you got what I was trying to say.
You can remove the onSubmit function from the form element and use it with onClick in the button element. Give it a try. I think it will work.
So here's the user function I'm trying to create:
1.) User double clicks on text
2.) Text turns into input field where user can edit text
3.) User hits enter, and upon submission, text is updated to be edited text.
Basically, it's just an edit function where the user can change certain blocks of text.
So here's my problem - I can turn the text into an input field upon a double click, but how do I get the edited text submitted and rendered?
My parent component, App.js, stores the function to update the App state (updateHandler). The updated information needs to be passed from the Tasks.jsx component, which is where the text input is being handled. I should also point out that some props are being sent to Tasks via TaskList. Code as follows:
App.js
import React, {useState} from 'react';
import Header from './Header'
import Card from './Card'
import cardData from './cardData'
import Dates from './Dates'
import Tasks from './Tasks'
import Footer from './Footer'
import TaskList from './TaskList'
const jobItems= [
{
id:8,
chore: 'wash dishes'
},
{
id:9,
chore: 'do laundry'
},
{
id:10,
chore: 'clean bathroom'
}
]
function App() {
const [listOfTasks, setTasks] = useState(jobItems)
const updateHandler = (task) => {
setTasks(listOfTasks.map(item => {
if(item.id === task.id) {
return {
...item,
chore: task.chore
}
} else {
return task
}
}))
}
const cardComponents = cardData.map(card => {
return <Card key = {card.id} name = {card.name}/>
})
return (
<div>
<Header/>
<Dates/>
<div className = 'card-container'>
{cardComponents}
</div>
<TaskList jobItems = {listOfTasks} setTasks = {setTasks} updateHandler = {updateHandler}/>
<div>
<Footer/>
</div>
</div>
)
}
export default App;
Tasks.jsx
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
TaskList.jsx
import React from 'react'
import Tasks from './Tasks'
function TaskList (props) {
const settingTasks = props.setTasks //might need 'this'
return (
<div>
{
props.jobItems.map(item => {
return <Tasks key = {item.id} item = {item} setTasks = {settingTasks} jobItems ={props.jobItems} updateHandler = {props.updateHandler}/>
})
}
</div>
)
}
export default TaskList
You forgot onChange handler on input element to set item's chore value.
Tasks.jsx must be like below
import React, {useState} from 'react'
function Tasks (props) {
const [isEditing, setIsEditing] = useState(false)
const handleInputChange = (e)=>{
// console.log( e.target.value );
// your awesome stuffs goes here
}
return(
<div className = 'tasks-container'>
{
isEditing ?
<form>
<input type = 'text' onChange={handleInputChange} defaultValue = {props.item.chore}/>
</form>
: <h1 onDoubleClick ={()=> setIsEditing(true)}>{props.item.chore}</h1>
}
</div>
)
}
export default Tasks
So, first of all, I would encourage you not to switch between input fields and divs but rather to use a contenteditable div. Then you just use the onInput attribute to call a setState function, like this:
function Tasks ({item}) {
return(
<div className = 'tasks-container'>
<div contenteditable="true" onInput={e => editTask(item.id, e.currentTarget.textContent)} >
{item.chore}
</div>
</div>
)
}
Then, in the parent component, you can define editTask to be a function that find an item by its id and replaces it with the new content (in a copy of the original tasks array, not the original array itself.
Additionally, you should avoid renaming the variable between components. (listOfTasks -> jobItems). This adds needless overhead, and you'll inevitably get confused at some point which variable is connected to which. Instead say, <MyComponent jobItems={jobItems} > or if you want to allow for greater abstraction <MyComponent items={jobItems} > and then you can reuse the component for listable items other than jobs.
See sandbox for working example:
https://codesandbox.io/s/practical-lewin-sxoys?file=/src/App.js
Your Task component needs a keyPress handler to set isEditing to false when enter is pressed:
const handleKeyPress = (e) => {
if (e.key === "Enter") {
setIsEditing(false);
}
};
Your updateHandler should also be passed to the input's onChange attribute, and instead of defaultValue, use value. It also needs to be reconfigured to take in the onChange event, and you can map tasks with an index to find them in state:
const updateHandler = (e, index) => {
const value = e.target.value;
setTasks(state => [
...state.slice(0, index),
{ ...state[index], chore: value },
...state.slice(index + 1)
]);
};
Finally, TaskList seems like an unnecessary middleman since all the functionality is between App and Task; you can just render the tasks directly into a div with a className of your choosing.
react-edit-text is a package I created which does exactly what you described.
It provides a lightweight editable text component in React.
A live demo is also available.