React Hooks: contentEditable not updating - javascript

function transformation(data) {
data = data + "it works";
return data;
}
export default function App() {
const [value, setValue] = useState("");
const handleChanges = (event) => {
setValue(event.currentTarget.innerHTML);
};
return (
<div
contentEditable
onChange={handleChanges}
html={transformation(value)}
/>
);
}
Why isn't onChange working whenever something is written in the div? I cannot launch the function "transformation". works fine... It seem to be an issue with contentEditable. I require this to be done with a hook.

You can use the react-contenteditable component which I think will do what you are looking for. The following code is your app with it implemented:
import React, { useState } from "react";
import ContentEditable from "react-contenteditable";
import "./styles.css";
function transformation(data) {
const test = `${data} It works`;
return test;
}
export default function App() {
const [value, setValue] = useState("");
const handleChanges = (event) => {
setValue(event.target.value);
};
return (
<ContentEditable
style={{ border: "2px solid black" }}
innerRef={this.contentEditable}
onChange={handleChanges}
html={transformation(value)}
/>
);
}
You can have a play around on the following: https://codesandbox.io/s/awesome-breeze-6rmih?file=/src/App.js:0-549
And you can read the component's documentation here: https://www.npmjs.com/package/react-contenteditable

The onChange attribute is only applicable to Form Field elements, such as input, select, textarea etc. Just change your div to any form element and it should work fine. :-)

I think that the correct answer is in this link:
how-to-listen-for-changes-in-a-contenteditable-element-in-react
since it does not use an external package.
example:
import React from "react";
export default function App() {
return(
<div contentEditable
onInput={(e) => console.log(e.currentTarget.textContent)}>
Text inside div
</div>
);
}

Related

ReactJS compontent state not update correctly

Consider I got a component called Test
import {useEffect, useState} from "react";
const Test = (props) => {
const [Amount, setAmount] = useState(1);
useEffect(()=>{
if(props.defaultAmount){
setAmount(props.defaultAmount)
}
props.getResult(Amount);
},[props, Amount])
return (
<>
<span>Amount is: {Amount}</span>
<input value={Amount} onChange={(e)=>setAmount(e.target.value)}/>
</>
)
}
export default Test;
I use this in two different components (actually my pages), one with defaultAmount another without.
Page 1:
<Test getResult={getAmountResult} defaultAmount={25}/>
But this not update result and it back to default one!
Page 2:
<Test getResult={getAmountResult} />
it works fine!
Working Demo
Is there any solution to avoid this?
try to change your code like this
import {useEffect, useState} from "react";
const Test = (props) => {
const [Amount, setAmount] = useState(1);
useEffect(() => {
props.getResult(Amount);
}, [Amount])
useEffect(()=>{
if(props.defaultAmount){
setAmount(props.defaultAmount)
}
},[props.defaultAmount])
return (
<>
<span>Amount is: {Amount}</span>
<input value={Amount} onChange={(e)=>setAmount(e.target.value)}/>
</>
)
}
export default Test;
in your current implementation you always overwrite the amount state with the default
Your useEffect function is the culprit. You're setting the Amount back to defaultAmount everytime Amount changes, thus overriding the user input.
Try updating the condition within useEffect before you set the value, to make sure you don't override the user input, something like:
useEffect(()=>{
if(props.defaultAmount && Amount === 1){ // Checking if the amount is still the initial value
setAmount(props.defaultAmount)
}
props.getResult(Amount);
},[props, Amount])
When input changes, setAmount called, it will update amount and trigger useEffect hook which will set amount to default value. Try this
import { useEffect, useState } from "react";
const Test = (props) => {
const [amount, setAmount] = useState(props.defaultAmount);
useEffect(() => {
if (amount) {
props.getResult(amount);
}
}, [amount, props]);
return (
<>
<span>Amount is: {amount}</span>
<input value={amount} onChange={(e) => setAmount(e.target.value)} />
</>
);
};
export default Test;

Why KeyboardEvent isn't working with this Input element in react?

I'm working with controlled input elements at work and I'm stuck.
Basically, I need to autofill some input elements in a form, but the problem is that I need to fill it in a way that simulates the user input (in this case, typing) in order to trigger the onChange function's logic. So, because of that. I need to emulate the typing behavior and not just set the value for the element.
Despite having searched for previous questions and reading docs about KeyboardEvent, I haven't been able to make this work.
Currently, I'm experimenting in a Codesandbox just for making things easier, but even with this simple environment, I can't manage to get this to work.
Here's the code and its Codesandbox link
import { useRef, useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [state, setState] = useState();
const inputRef = useRef();
const event = new KeyboardEvent("keypress", { key: 99 });
useEffect(() => {
inputRef.current.dispatchEvent(event);
}, [inputRef]);
const onChange = (e) => {
setState(e.target.value);
};
return (
<div className="App">
<h1>{state}</h1>
<input
type="text"
id="name"
onChange={onChange}
ref={inputRef}
value={state}
/>
</div>
);
}
Hopefully one of you guys could give me a hand with this.
Thanks for reading!
Related to the comments:
I think that it shouldn't be necessary to be dispatching a keypress event to get your special effect logic to run.
For example, you can use a useEffect which just runs on initial render to trigger whatever special logic you want -- and this way you can just have a regular initial value for the form state.
import { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
// In the useState call, you can initialize the value.
const [state, setState] = useState("initial value");
const specialEffectFunction = () => {
// here's the code for the special effect you want to run on load
console.log('this is the special onChange effect')
}
useEffect(() => {
// This will trigger the special function which you want to run
// when the app loads
specialEffectFunction();
// if it really HAS to be the `onChange` function that's called,
// then you'll need to call that with a fake ChangeEvent.. but I don't
// think that should be necessary...
}, [])
const onChange = (e) => {
setState(e.target.value);
};
return (
<div className="App">
<h1>{state}</h1>
<input
type="text"
id="name"
onChange={onChange}
value={state}
/>
</div>
);
}
I couldn't fix the problem with Keyboard Event for my lack of knowledge about it, but I hope I managed to solve the problem of emulating a human autofill the input using the below code.
function AutoFillInput({ finalValue }: { finalValue: string }) {
const [inputValue, setInputValue] = useState('');
const [sliceStart, setSliceStart] = useState(0);
const changeHandler = useCallback((event) => {
setInputValue(event.target.value);
}, []);
useEffect(function handleFinalValueChange() {
setInputValue('');
if (sliceStart < finalValue.length)
setSliceStart(x => x + 1);
}, [finalValue]);
useEffect(function handleSlice() {
setInputValue(finalValue.slice(0, sliceStart));
if (sliceStart < finalValue.length) {
setTimeout(() => {
setSliceStart(x => x + 1);
}, 800);
}
}, [sliceStart]);
return (
<input
value={inputValue}
onChange={changeHandler}
placeholder={'Auto fill input'}
/>
)
}
function App() {
return (
<div >
<AutoFillInput finalValue={'hello world'} />
</div>
);
}
export default App;

React read input values from multiple input text fields

Hi I have a little meme editor using the imgflip public api. I usually develop using Angular but I'm trying to learn react so I'm a little lost right now.
On my project when I load the page I get a list of all the meme templates available, then when you select one template you have the template and one text field for each meme text line. The number of input texts changes on each template this is where I'm stuck.
The idea is to get all the input text values, send it to the api and show the generated meme to the user.
This is my code right now:
App.js
import { useEffect, useState } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
import memeService from './services/memeService';
import Meme from './components/Meme';
import './App.css';
import Editor from './components/Editor';
function App() {
const [memes, setMemes] = useState([]);
useEffect(() => {
(async function getData() {
await getMemes();
})();
}, []);
const getMemes = async () => {
const results = await memeService.getMemes();
setMemes(results.data.data.memes);
}
return (
<>
<Router>
<Switch>
<Route path="/:id/edit" children={<Editor />} />
<Route path="/">
<div className='container'>
{memes.map(meme => <Meme key={meme.id} meme={meme} />)}
</div>
</Route>
</Switch>
</Router>
</>
)
}
export default App;
Editor.js
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import memeService from './../services/memeService';
const Editor = () => {
const [meme, setMeme] = useState({});
const {id } = useParams()
const getMeme = async () => {
setMeme(await memeService.getMeme(id));
}
useEffect(getMeme, [id]);
const TextBox = () => {
const inputs = [];
for(let i = 0; i < meme.box_count; i++){
inputs.push(<input key={i} type='text' />);
}
return (
<>
{inputs.map(input => {return input})}
</>
)
}
const generateMeme = () => {
console.log('generating meme');
}
return (
<>
<div className='container'>
<div className='meme'>
<img alt={meme.name} src={meme.url} />
</div>
<div className='text'>
<TextBox />
</div>
</div>
<button onClick={generateMeme}>Save</button>
</>
)
}
export default Editor;
I'm not proud at all of the TextBox function that renders the input text fields but for now I'm mostly concerned about making this work.
THe point where I'm stuck is on the Editor.js I need to get all the text on the input text field that I have on the editor to send it to the API. On other tutorials that I followed I didi it using the app's state using the onChange event so when the user types on the text submit the states gets updated and when clicking on the submit button I just use the current state but on this scenario I don't see it possible as there's multiple and different inputs.
By the way this is the API I'm using: https://imgflip.com/api
First, you need to keep track of values in the inputs, by add an array to the TextBox and making inputs controlled.
Second, you need to pass the values to the parent. For that you can add a handler method, which will remember the values into a ref, like
const values = useRef()
handleChange(newValues){
values.current(newValues)
}
Then you pass handleChange as a prop and call it after setValues. And on submit you'll have your values in values.current
The complete TextBox:
const TextBox = (props) => {
const [values, setValues] = useState([])
const inputs = [];
useEffect(()=>{
props.onChange && props.onChange(values)
}, [values])
function handleInput(e, i)
{
setValues(v =>{
const temp=[...v];
temp[i]=e.target.value;
return temp})
}
for(let i = 0; i < meme.box_count; i++){
inputs.push(<input key={i} type='text' value={values[i]} onChange={(e) => handleInput(e,i) } />);
}
return (inputs)
}

Change color using useState on React using Material UI

I like to change the color of the icon using useState by clicking on the icon, I added a click handler on the icon.
Here is my code:
import React, { useState } from 'react';
import './App.css';
import ThumbUpIcon from '#material-ui/icons/ThumbUp';
function App() {
const[likeColor, setLikeColor] = useState('');
const colorStyle = {color:"blue"}
const handleClick = () => {
const color = likeColor ? '' : colorStyle;
setLikeColor(color);
}
return (
<div className="App">
<ThumbUpIcon onClick={handleClick} style={{likeColor}}></ThumbUpIcon>
</div>
);
}
export default App;
You have an object inside of an object, I would try as:
<ThumbUpIcon onClick={handleClick} style={likeColor}></ThumbUpIcon>
See the difference in style attribute.
There is issue "prop style must be object (css concept), so it should style={likeColor}". By the way, you should init value likeColor is a object instead empty string. So, i recommend you should change way like color work.
function App() {
const[isLike, setIsLike] = useState(false);
const colorStyle = {color:"blue"}
const handleClick = () => {
setIsLike(!isLike);
}
return (
<div className="App">
<ThumbUpIcon onClick={handleClick} style={isLike ? colorStyle : null}></ThumbUpIcon>
</div>
);
}

Binding API Data from React Parent Component to Child Components

I'm new to React and am tripping over this issue.
Have read couple of tutorials and questions here to find out about how Parent & Child Components should communicate. However, I am unable to get the data to populate the fields + make it editable at the same time. I'll try explain further in code below:
Parent Component:
...imports...
export default class Parent extends Component {
constructor(props) {
this.state = {
data: null
};
}
componentDidMount() {
API.getData()
.then((response) => {
this.setState({ data: response });
// returns an object: { name: 'Name goes here' }
})
}
render() {
return (
<Fragment>
<ChildComponentA data={this.state.data} />
<ChildComponentB data={this.state.data} />
</Fragment>
);
}
}
Input Hook: (source: https://rangle.io/blog/simplifying-controlled-inputs-with-hooks/)
import { useState } from "react";
export const useInput = initialValue => {
const [value, setValue] = useState(initialValue);
return {
value,
setValue,
reset: () => setValue(""),
bind: {
value,
onChange: event => {
setValue(event.target.value);
}
}
};
};
ChildComponent:* (This works to allow me to type input)
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput('');
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
ChildComponent Component:
(Trying to bind API data - Input still editable but data is still not populated even though it is correctly received.. The API data takes awhile to be received, so the initial value is undefined)
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput(props.data && props.data.name || '');
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
ChildComponent Component:
(Trying to use useEffect to bind the data works but input field cannot be typed..)
I believe this is because useEffect() is trigged every time we type.. and props.data.name is rebinding its original value
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput(props.data && props.data.name || '');
useEffect(() => {
if(props.data) {
setValue(props.data.name);
}
});
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
I can think of a few tricks like making sure it binds only once etc.. But I'm not sure if it is the correct approach. Could someone share some insights of what I could be doing wrong? And what should be the correct practice to do this.
To iterate, I'm trying to bind API data (which takes awhile to load) in parent, and passing them down as props to its children. These children have forms and I would like to populate them with these API data when it becomes available and yet remain editable after.
Thanks!
Basic way to create your Parent/Child Component structure is below, I believe. You don't need a class-based component for what you are trying to achieve. Just add an empty array as a second argument to your useEffect hook and it will work as a componentDidMount life-cycle method.
Parent component:
import React, {useState, useEffect} from 'react';
export default const Parent = () => {
const [data, setData] = useState({});
const [input, setInput] = useState({});
const inputHandler = input => setInput(input);
useEffect(() => {
axios.get('url')
.then(response => setData(response))
.catch(error => console.log(error));
}, []);
return <ChildComponent data={data} input={input} inputHandler={inputHandler} />;
};
Child Component:
import React from 'react';
export default const ChildComponent = props => {
return (
<div>
<h1>{props.data.name}</h1>
<input onChange={(e) => props.inputHandler(e.target.value)} value={props.input} />
</div>
);
};

Categories

Resources