How can I show a button when another element has focus? - javascript

I have multiple rows, each row contains two text inputs and a button. When user focuses on one of the inputs, the button should be shown. When elements lose focus, the button should become invisible once again. My best attempt:
const Input = ({inputRef}) => {
return (
<>
<h1>Input</h1>
<input type="text" ref={inputRef}/>
</>
)
}
export default () => {
const firstRef = useRef(null);
const secondRef = useRef(null);
const it = useRef(null);
const [editing, setEditing] = useState(false);
function handleClick(e) {
firstRef.current.focus();
}
function handleSave() {
console.log("saving!");
}
function checkFocus(e) {
if (!it.current.contains(document.activeElement)) {
setEditing(false);
} else {
setEditing(true);
}
}
useEffect(() => {
document.body.addEventListener("focus", checkFocus, true);
return () => {
document.body.removeEventListener("focus", checkFocus, true);
}
}, []);
return (
<div ref={it}>
<Input inputRef={firstRef}/>
<Input inputRef={secondRef}/>
<button type="button" onClick={handleSave} style={{visibility: editing ? "visible" : "hidden"}}>Save</button>
<button type="button" onClick={handleClick}>Edit</button>
</div>
)
}
Is there any better/more elegant and efficient way of achieving this?

Here's a solution to what you're attempting using only CSS, which in my opinion makes it more elegant (and ever so slightly more performant, but really this is negligible).
https://codepen.io/danny_does_stuff/pen/QWMprMJ
<div>
<input id="input1" />
<input id="input2" />
<button id="save-button">Save</button>
<button id="edit-button">Edit</button>
</div>
<style>
input#input2:focus + button#save-button {
visibility: hidden;
}
</style>
If you wanted to do it in a more React way, you could do what Marco B suggested in his answer

You can use onBlur and onFocus events.
This should work as expected, just adapt the logic on your component
EDIT
Edited the onBlur method.
const INITIAL_STATE = {
input: ''
}
export default function App() {
const [show, setShow] = useState(false);
const [value, setValue] = useState(INITIAL_STATE);
const handleChange = (e) => {
const { value, name } = e.target;
setValue(prevState => ({ ...prevState, [name]: value }))
}
const onBlur = () => {
if (!value.input) {
setShow(false)
}
}
return (
<>
<Input name="input" onChange={handleChange} value={value.input} onFocus={() => setShow(true)} onBlur={onBlur} />
{show && <button>TEST</button>}
</>
);
}
const Input = (props) => {
return (
<>
<h1>Input</h1>
<input {...props} type="text" />
</>
);
};

Related

How to get inputs data from multiple text box in for loop in React js and pass it to Api

I am trying to build a quiz application where I want to generate no of Question input fields based on admin inputs.
So suppose the admin enters 10 questions for the quiz.
Then I am rendering the form inside for loop for 10 Questions and their answers respectively.
The problem I am facing is I am not able to get all values from input fields.
Below is my demo code:
import { useState } from "react";
const MyComponent = () => {
const [inputs, setInputs] = useState({});
const handleChange = (e) =>
setInputs((prevState) => ({
...prevState,
[e.target.name]: e.target.value
}));
const finalData = (e) => {
e.preventDefault();
console.log("data", inputs);
};
function buildRows() {
const arr = [];
for (let i = 1; i <= 3; i++) {
arr.push(
<div key={i} id={i}>
<input name="Question" onChange={handleChange} />
<input name="option1" onChange={handleChange} />
<input name="option2" onChange={handleChange} />
<input name="option3" onChange={handleChange} />
<input name="option4" onChange={handleChange} />
</div>
);
}
return arr;
}
return (
<>
{buildRows()}
<button
onClick={(e) => finalData(e)}
variant="contained"
className="button-left"
sx={{ marginRight: 3.5 }}
>
Submit Quiz Questions
</button>
</>
);
};
export default MyComponent;
You could use the id (or any other unique property, a unique name would probably be preferred) you're giving your div and build your object with that as an array index like so:
const handleChange = (e) => {
const parent = e.currentTarget.parentNode;
const id = parent.id;
setInputs((prevState) => ({
...prevState,
[id]: {
...prevState[id],
[e.target.name]: e.target.value
}
}));
};
This produces an object like this:
{
"1":{
"Question":"1",
"option1":"2",
"option2":"3",
"option3":"4",
"option4":"5"
},
"2":{
"Question":"6",
"option1":"7",
"option2":"8",
"option3":"9",
"option4":"11"
},
"3":{
"Question":"22",
"option1":"33",
"option2":"44",
"option3":"55",
"option4":"66"
}
}

Incorrect validation when trying to send data from additional inputs

Hello everyone and thank you for reading this! Here is my problem that i can't solve:
My application has the following functionality:
There are 2 inputs, then a button, when clicked, 2 more inputs appear and a button to send data from all inputs to the console, however, in the additional field, one input is required. This is where my problem arises: now, if I called additional inputs and filled in all the data, they are transferred to the console, if I didn’t fill in the required field, an error message goes to the console, BUT. I also need, in the event that I did NOT call additional inputs, the data of 2 basic inputs was transferred to the console. At the moment I can't figure it out.
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
import produce from "immer";
const FunctionalBlock = ({
id,
idx,
isDeleted,
toggleBlockState,
additionalValue,
additionalTitle,
setNewBlock,
index,
}) => {
return (
<div
style={{
display: "flex",
maxWidth: "300px",
justifyContent: "space-between",
}}
>
{!isDeleted ? (
<React.Fragment>
<strong>{idx}</strong>
<input
type="text"
value={additionalTitle}
onChange={(e) => {
const additionalTitle = e.target.value;
setNewBlock((currentForm) =>
produce(currentForm, (v) => {
v[index].additionalTitle = additionalTitle;
})
);
}}
/>
<input
type="text"
value={additionalValue}
onChange={(e) => {
const additionalValue = e.target.value;
setNewBlock((currentForm) =>
produce(currentForm, (v) => {
v[index].additionalValue = additionalValue;
})
);
}}
/>
<button onClick={toggleBlockState}>now delete me</button>
</React.Fragment>
) : (
<button onClick={toggleBlockState}>REVIVE BLOCK</button>
)}
</div>
);
};
const Application = () => {
const [newBlock, setNewBlock] = useState([]);
const [firstInput, setFirstInput] = useState("");
const [secondInput, setSecondInput] = useState("");
const getNewBlock = (idx) => ({
id: Date.now(),
idx,
isDeleted: false,
additionalValue: "",
additionalTitle: "",
});
const toggleIsDeletedById = (id, block) => {
if (id !== block.id) return block;
return {
...block,
isDeleted: !block.isDeleted,
};
};
const createOnClick = () => {
const block = getNewBlock(newBlock.length + 1);
setNewBlock([...newBlock, block]);
};
const toggleBlockStateById = (id) => {
setNewBlock(newBlock.map((block) => toggleIsDeletedById(id, block)));
};
const showInputData = () => {
newBlock.map((item) => {
if (item.additionalTitle.length < 3) {
console.log("it is less than 3");
} else if (!item.additionalTitle && !item.additionalValue) {
console.log(firstInput, secondInput);
} else {
console.log(
firstInput,
secondInput,
item.additionalTitle,
item.additionalValue
);
}
});
};
return (
<div>
<div>
<input
type="text"
value={firstInput}
onChange={(e) => {
setFirstInput(e.target.value);
}}
/>
<input
type="text"
value={secondInput}
onChange={(e) => {
setSecondInput(e.target.value);
}}
/>
</div>
<div>
<button onClick={createOnClick}>ADD NEW INPUTS</button>
</div>
<div>
{newBlock.map((block, index) => (
<FunctionalBlock
key={index}
{...block}
toggleBlockState={() => toggleBlockStateById(block.id)}
setNewBlock={setNewBlock}
index={index}
/>
))}
</div>
<button onClick={showInputData}>send data</button>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Application />);
Here is this code on sandbox for those who decided to help me. Thank you!
https://codesandbox.io/s/vigilant-booth-xnef6t

Array items only appearing once when filtering in React

In this React code what I'm trying to do is getting all items from the list that match with what type in the text input that's in the setManName function (also there is one in setModeName function). It works, but when I delete the text input and start over, the items disappear and will not appear anymore, not showing on the screen unless I reload the page again and start over again. I am using inludes() method, which works fine, but once I delete a letter or whole word and start over again it doesn't work. What's the problem here? Should I be using a different approach? Like another useEffect or something?
import React, { useState, useEffect } from "react";
import "./style.css";
export default function App() {
const [items, setItems] = useState([])
const [openFilterCt, setOpenFilterCt] = useState(false)
const [term1, setTerm1] = useState()
const [term2, setTerm2] = useState()
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then(res => res.json())
.then(data => {
setItems(data)
})
}, [])
function setManName(e) {
setTerm1(e.target.value);
let u = items.filter(item => {
**return item.make.includes(e.target.value)**
})
setItems(u)
}
function setModName(e) {
setTerm2(e.target.value);
let u = items.filter(item => {
**return item.model.includes(e.target.value)**
})
setItems(u)
}
function hi() {
setOpenFilterCt(!openFilterCt)
}
return (
<div>
<h1>React Search & Filter</h1>
<div>
<h3 onClick={hi}>Filter</h3>
<div className={openFilterCt ? "show" : "hide"}>
<label>
Name of manufacturer: <input type="text" value={term1} onChange={setManName} />
</label>
<br />
<label>
Name of model: <input type="text" value={term2} onChange={setModName} />
</label>
</div>
</div>
{items.slice(0, 50).map((a, index) => {
return (
<div key={index} style={{border: "1px solid black", margin: "10px", padding: "5px"}}>
<p>Manufacturer: {a.make[0].toUpperCase() + a.make.slice(1)}</p>
<p>Model: {a.model}</p>
</div>
)
})}
</div>
);
}
You are overwriting the items object, so any items not in a search will not show up even after deleting characters. This solution will dynamically filter the items, rather than removing them from the array.
Additionally, you should provide a default value to the term1 and term2 states. Without a default value, the inputs are switching from uncontrolled to controlled inputs, a practice that is discouraged in React.
See this Codesandbox.
import React, { useState, useEffect } from "react";
import "./style.css";
export default function App() {
const [items, setItems] = useState([]);
const [openFilterCt, setOpenFilterCt] = useState(false);
const [term1, setTerm1] = useState("");
const [term2, setTerm2] = useState("");
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then((res) => res.json())
.then((data) => {
setItems(data);
});
}, []);
function setManName(e) {
setTerm1(e.target.value);
}
function setModName(e) {
setTerm2(e.target.value);
}
function filterItems(item) {
if (term1 && !item.make.includes(term1)) return false;
if (term2 && !item.model.includes(term2)) return false;
return true;
}
function hi() {
setOpenFilterCt(!openFilterCt);
}
return (
<div>
<h1>React Search & Filter</h1>
<div>
<h3 onClick={hi}>Filter</h3>
<div className={openFilterCt ? "show" : "hide"}>
<label>
Name of manufacturer:{" "}
<input type="text" value={term1} onInput={setManName} />
</label>
<br />
<label>
Name of model:{" "}
<input type="text" value={term2} onInput={setModName} />
</label>
</div>
</div>
{items
.filter(filterItems)
.slice(0, 50)
.map((a, index) => {
return (
<div
key={index}
style={{
border: "1px solid black",
margin: "10px",
padding: "5px"
}}
>
<p>Manufacturer: {a.make[0].toUpperCase() + a.make.slice(1)}</p>
<p>Model: {a.model}</p>
</div>
);
})}
</div>
);
}
I have just fixed it for your model search, you can do the same for the manufacturer search. There may be more optimal ways, but this is something I worked it out.
What you need to do is preserve your original list. .filter() actually changes the original list, and when the response is blank, the original data is gone. So I just preserved the old data
const [orItem, setOrItems] = useState([]);
const prevList = orItem;
function setModName(e) {
setTerm2(e.target.value);
let u = prevList.filter((item) => {
return item.model.includes(e.target.value);
});
setItems(u);
}
You can see you code in action here for model search:
https://codesandbox.io/s/elegant-mcclintock-46ncr?file=/src/App.js
First, you should not modify the original array items. You need to create another one(another state variable) filteredItems so you can reset to the original state, also I believe there is another error here item.model.includes(e.target.value), it will always return false if the text is empty.
function setManName(e) {
setTerm1(e.target.value);
if(e.target.value){
let u = items.filter(item => {
return item.make.includes(e.target.value)
})
setFilteredItems(u)
}else{
setItems(items)
}
}
Also useEffect hook should be something like this:
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then(res => res.json())
.then(data => {
setItems(data)
setFilteredItems(data)
})
}, [])
And make sure to map over filteredItems.
The problem here is that you are resetting the value that you receive from the given URL. You should be maintaining a separate list for visibility you could go by approach 1. given below. Its the best I could do without modifying lots of your code however this is typically an over/mis use of states. Remember React is called react because of its amazing capability to react when the state changes.
The Approach2 realizes just that, you can be smart with the filter and alter it as you need. your search to behave.
// Approach 1
import React, { useState, useEffect } from "react";
// import "./style.css";
export default function App() {
const [items, setItems] = useState([]);
const [visibleItems, setVisibleItems] = useState([]);
const [openFilterCt, setOpenFilterCt] = useState(false)
const [term1, setTerm1] = useState()
const [term2, setTerm2] = useState()
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then(res => res.json())
.then(data => {
setItems(data);
setVisibleItems(data);
})
}, [])
function setManName(e) {
// setTerm1(e.target.value);
let u = items.filter(item => {
if(e.target.value){
return item.make.includes(e.target.value)
}
return true;
})
setVisibleItems(u)
}
function setModName(e) {
// setTerm2(e.target.value);
let u = items.filter(item => {
return item.model.includes(e.target.value)
})
setVisibleItems(u)
}
function hi() {
setOpenFilterCt(!openFilterCt)
}
return (
<div>
<h1>React Search & Filter</h1>
<div>
<h3 onClick={hi}>Filter</h3>
<div className={openFilterCt ? "show" : "hide"}>
<label>
Name of manufacturer: <input type="text" value={term1} onChange={setManName} />
</label>
<br />
<label>
Name of model: <input type="text" value={term2} onChange={setModName} />
</label>
</div>
</div>
{visibleItems.slice(0, 50).map((a, index) => {
return (
<div key={index} style={{border: "1px solid black", margin: "10px", padding: "5px"}}>
<p>Manufacturer: {a.make[0].toUpperCase() + a.make.slice(1)}</p>
<p>Model: {a.model}</p>
</div>
)
})}
</div>
);
}
// Approach2
import React, { useState, useEffect } from "react";
// import "./style.css";
export default function App() {
const [items, setItems] = useState([]);
const [openFilterCt, setOpenFilterCt] = useState(false);
const [term1, setTerm1] = useState("");
const [term2, setTerm2] = useState("");
useEffect(() => {
fetch("https://private-anon-af560a53c6-carsapi1.apiary-mock.com/cars")
.then((res) => res.json())
.then((data) => {
setItems(data);
});
}, []);
function setManName(e) {
setTerm1(e.target.value);
}
function setModName(e) {
setTerm2(e.target.value);
}
function hi() {
setOpenFilterCt(!openFilterCt);
}
return (
<div>
<h1>React Search & Filter</h1>
<div>
<h3 onClick={hi}>Filter</h3>
<div className={openFilterCt ? "show" : "hide"}>
<label>
Name of manufacturer:{" "}
<input type="text" value={term1} onChange={setManName} />
</label>
<br />
<label>
Name of model:{" "}
<input type="text" value={term2} onChange={setModName} />
</label>
</div>
</div>
{items
.filter((item) => {
return item.make.includes(term1) && item.model.includes(term2);
})
.slice(0, 50)
.map((a, index) => {
return (
<div
key={index}
style={{
border: "1px solid black",
margin: "10px",
padding: "5px"
}}
>
<p>Manufacturer: {a.make[0].toUpperCase() + a.make.slice(1)}</p>
<p>Model: {a.model}</p>
</div>
);
})}
</div>
);
}

onKeyDown get value from e.target.value?

What's wrong with my way to handle input in react? I want to detect keycode and prevent them to be entered into the input, but now below code doesn't seem working.
const Input = ({ placeholder }) => { const [inputValue, setInputValue] = useState("");
const handleKeyDown = e => {
console.log(e.target.value);
if ([188].includes(e.keyCode)) {
console.log("comma");
} else {
setInputValue(e.target.value);
} };
return (
<div>
<input
type="text"
value={inputValue}
onKeyDown={handleKeyDown}
placeholder={placeholder}
/>
</div> ); };
https://codesandbox.io/s/ancient-waterfall-43not?file=/src/App.js
you need to call e.preventDefault(), but also you need to add onChange handler to input:
const handleKeyDown = e => {
console.log(e.key);
if ([188].includes(e.keyCode)) {
console.log("comma");
e.preventDefault();
}
};
const handleChange = e => setInputValue(e.target.value);
...
<input
type="text"
value={inputValue}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder={placeholder}
/>
When you update the value of input, you should use onChange().
But, If you want to catch some character and treat about that, you should use onKeyDown().
So, in your case, you should use both.
this is an example code about backspace.
const Input = (props) =>{
const [value, setValue] = React.useState('');
function handleChange(e){
setValue(e.target.value);
}
function handleBackSpace(e){
if(e.keyCode === 8){
//Do something.
}
}
return (
<div>
<input onChange={handleChange} onKeyDown={handleBackSpace} value={value} type="text" />
</div>
)
}
```
Pass the value from Parent to child. Please check the below code.
import React, { useState } from "react";
import "./styles.css";
const Input = ({ placeholder, inputValue, handleKeyDown }) => {
return (
<div>
<input
type="text"
value={inputValue}
onKeyDown={handleKeyDown}
placeholder={placeholder}
/>
</div>
);
};
export default function App() {
const [inputValue, setInputValue] = useState();
const handleKeyDown = e => {
console.log(e.keyCode);
console.log(e.target.value);
if ([40].includes(e.keyCode)) {
console.log("comma");
} else {
setInputValue(e.target.value);
}
};
return (
<div className="App">
<Input
placeholder="xx"
value={inputValue}
handleKeyDown={handleKeyDown}
/>
</div>
);
}
onKeyDown, onKeyUp, and onKeyPress contain the old value of the target element.
onInput event gets the new value of the target element.
check the below link I add some console log. which help you to understand which event contains the value
https://codesandbox.io/s/ecstatic-framework-c4hkw?file=/src/App.js
I think you should not use the onKeyDown event on this case to filter your input. The reason is that someone could simply copy and paste the content into the input. So it would not filter the comma character.
You should use the onChange event and add a Regex to test if the input is valid.
const Input = ({ placeholder }) => {
const [inputValue, setInputValue] = useState("");
const handleChange = e => {
const filteredInput = e.target.value.replace(/[^\w\s]/gi, "");
setInputValue(filteredInput);
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder={placeholder}
/>
</div>
);
};
But how it works...
So the regex is currently allowing any word, digit (alphanumeric) and whitespaces. You could for example extend the whitelist to allow # by doing const filteredInput = e.target.value.replace(/[^\w\s#]/gi, ""); Any rule inside the [^] is allowed. You can do some regex testing here https://regexr.com/55rke
Also you can test my example at: https://codesandbox.io/s/nice-paper-40dou?file=/src/App.js

Bind child component click handler to parent state

None of the other SO answers have helped, so I think I'm missing something conceptually.
I have a Parent (Wrapper) component, and a Child (Input) component. The Parent passes a function down to the child:
const Wrapper = () => {
const [dictionary, setDictionary] = useState([{ word: "init", definition: "def init" }]);
const handleWordChange = (e, value) => {
e.preventDefault();
/// IS NEVER TRIGGERED
};
return (
<Input setDictionary={{ setDictionary }} onChange={handleWordChange} />
)
}
The child component handles its own state, but is supposed to update the Parent props by calling the setDictionary function:
const Input = props => {
const [definition, setDefinition] = useState("");
const [word, setWord] = useState("");
const handleSubmit = e => {
const { setDictionary } = props.setDictionary;
e.preventDefault();
setDictionary([{ word, definition }]);
}
return (
<form onSubmit={handleSubmit}>
<input
name='word'
onChange={e => setWord(e.target.value)}
onFocus={() => setWord("")}
placeholder='Word'
type='text'
value={word}
/>
<input
name='definition'
onChange={e => setDefinition(e.target.value)}
onFocus={() => setDefinition("")}
placeholder='Definition'
type='text'
value={definition}
/>
<input type='submit' value='Submit' />
</form>
)
}
Other answers I have seen suggest to pass a callback to the Child (setDictionary), but the onChange handler is never called on change. I've also tried to use onSubmit instead.
How do I successfully update dictionary?
I know the above creates a dependency of the Child to the Parent, is there a better programmatic way to achieve this, considering that I eventually need to pass down dictionary to a 2nd child?
You cannot assign child's onChange() event handler this way.
Instead, you refer to child event handlers as props and bind parent callbacks to those props.
The concept is known as lifting state up.
Complete live-demo of your use case you may find below:
const { render } = ReactDOM,
{ useState } = React
const Input = ({onInput}) => {
const [word, setWord] = useState(''),
[definition, setDefinition] = useState('')
return (
<form onSubmit={e => (e.preventDefault(), onInput(word, definition))}>
<label>
Word:
<input onChange={({target:{value}}) => setWord(value)} />
</label>
<label>
Definition:
<input onChange={({target:{value}}) => setDefinition(value)} />
</label>
<input type="submit" value="Submit" />
</form>
)
}
const List = ({list}) => (
<ul>
{
list.map(({word,definition},key) => <li {...{key}}><strong>{word}</strong> - {definition}</li>)
}
</ul>
)
const Parent = () => {
const [dictionary, setDictionary] = useState([]),
onDicionaryItemSubmit = (word,definition) => setDictionary([...dictionary, {word,definition}])
return (
<div>
<Input onInput={onDicionaryItemSubmit} />
<List list={dictionary} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
//there is not props.onChange here in this component
const Input = props => {
const [definition, setDefinition] = useState("");
const [word, setWord] = useState("");
const handleSubmit = e => {
const { setDictionary } = props.setDictionary;
e.preventDefault();
setDictionary([{ word, definition }]);
//like here
props.onChange(any arguments);
}
return (
<form onSubmit={handleSubmit}>
<input
name='word'
onChange={e => {
setWord(e.target.value)
props.onChange();
}}
onFocus={() => setWord("")}
placeholder='Word'
type='text'
value={word}
/>
<input
name='definition'
onChange={e => {
setDefinition(e.target.value)
props.onChange();
}}
onFocus={() => setDefinition("")}
placeholder='Definition'
type='text'
value={definition}
/>
<input type='submit' value='Submit' />
</form>
)
}
Use parent onChange() method in your Input Component than it will be triggered if you didn't call that method than how it will triggered i hope this will help you.
You're not even triggering onChange passed to component
<Input setDictionary={{ setDictionary }} onChange={handleWordChange} />
you have to do exactly as you named the prop like props.onChange
//there is no props.onChange here in this component
const Input = props => {
const [definition, setDefinition] = useState("");
const [word, setWord] = useState("");
const handleSubmit = e => {
const { setDictionary } = props.setDictionary;
e.preventDefault();
setDictionary([{ word, definition }]);
//like here
props.onChange(any arguments);
}
return (
<form onSubmit={handleSubmit}>
<input
name='word'
onChange={e => setWord(e.target.value)}
onFocus={() => setWord("")}
placeholder='Word'
type='text'
value={word}
/>
<input
name='definition'
onChange={e => setDefinition(e.target.value)}
onFocus={() => setDefinition("")}
placeholder='Definition'
type='text'
value={definition}
/>
<input type='submit' value='Submit' />
</form>
)
}
If i rename
<Input setDictionary={{ setDictionary }} onInputChanged={handleWordChange} />
i'd call it like
const handleSubmit = e => {
const { setDictionary } = props.setDictionary;
e.preventDefault();
setDictionary([{ word, definition }]);
//like here
props.onInputChanged(any arguments);
}
Error:- not calling onChange function in <Input/> and setting state of dictionary in <Wrapper />. This is the working solution of your query.
const {useState} = React;
const Wrapper = () => {
const [dictionary, setDictionary] = useState([
{ word: "computer", definition: "an electronic device for storing and processing data" }
]);
const handleWordChange = (e, value) => {
e.preventDefault();
let updateDictionary = [...dictionary];
updateDictionary.push(value);
setDictionary(updateDictionary);
// console.log(updateDictionary);
/// IS NEVER TRIGGERED
};
return (
<React.Fragment>
<Input onChange={handleWordChange} />
{dictionary.length > 0 ? (
<table>
<tr>
<th>WORD</th>
<th>DEFINITION</th>
</tr>
{dictionary.map(datum => (
<tr>
<td>{datum.word}</td>
<td>{datum.definition}</td>
</tr>
))}
</table>
) : null}
</React.Fragment>
);
};
const Input = props => {
const [definition, setDefinition] = useState("");
const [word, setWord] = useState("");
const handleSubmit = e => {
e.preventDefault();
props.onChange(e, { word, definition });
};
return (
<form onSubmit={handleSubmit}>
<input
name="word"
onChange={e => setWord(e.target.value)}
onFocus={() => setWord("")}
placeholder="Word"
type="text"
value={word}
/>
<input
name="definition"
onChange={e => setDefinition(e.target.value)}
onFocus={() => setDefinition("")}
placeholder="Definition"
type="text"
value={definition}
/>
<input type="submit" value="Submit" />
</form>
);
};
ReactDOM.render(<Wrapper />, document.getElementById('root'));
table,
th,
td {
border: 1px solid black;
}
table {
margin-top: 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>

Categories

Resources