How to store changes in state using contenteditable element? - reactjs

How to store changes in state using contenteditable element?
This code store it nicely, but the cursor changes to the beginning of the element.
Do I have to code the cursor position or is there an easy solution?
And no I wont use a form element.
import React, { useState } from 'react';
const About = () => {
const [text, setText] = useState('Hallo');
return (
<div className="about">
<h2>About</h2>
<div className="title column m-0"
contentEditable={true}
suppressContentEditableWarning={true}
onInput={(e) => { setText(e.target.innerHTML) }}
onBlur={(e) => { setText(e.target.innerHTML) }}
dangerouslySetInnerHTML={{ __html: text }}
></div>
</div >
)
}
export default About;

If you only want to store changes and not to have a controlled component you can use onInput prop. It will store the changed text into your state.
<div
contentEditable={true}
suppressContentEditableWarning={true}
onInput={e => setText(e.currentTarget.textContent)}
></div>
P.S: The reason that cursor loses focus is that when you dangerouslySetInnerHTML, you basically are re-mounting every data that you have in your div including the cursor position. That's one of the reasons which makes setting HTML in this way, not recommended.

Related

React cannot get data from fetch api first time. I try my best but it didn't fetch data for the first time

I didn't get data for the first time.
Here is my screen shot when I search for the first time it return undefined and when I search for second time it return proper data.
How to I fix this problem. And please also explain what does it happens. I search this behavior from 2 days but I didn't find any solution even from stack overflow.
Here is my code.
import logo from './logo.svg';
import './App.css';
import Navbar from './components/Navbar';
import { useEffect, useMemo, useState } from 'react'
function App() {
const [searchWord, setSearchWord] = useState('');
const [responseWord, setResponseWord] = useState();
const [isLoad, setIsLoad] = useState(false)
const [urlLink, setUrlLink] = useState('')
async function fetchWord(word) {
console.log(isLoad)
const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
const data = await res.json();
setIsLoad(true)
setResponseWord(data)
console.log(responseWord)
console.log(isLoad)
}
return (
<>
<Navbar />
<div className="container mt-4">
<div className="row">
<div className="column bg-success text-light text-center col-3" style={{ height: "100vh" }}>
<h4> English Dictionary</h4>
</div>
<div className="column col-5 bg-light">
{
isLoad &&
<>
<h3 className='word'>{responseWord.word}</h3>
</>
}
</div>
<div className="row col-3" style={{ height: 50 }}>
<form className="d-flex" role="search" onSubmit={(e) => e.preventDefault()}>
<input className="form-control mr-sm-2" placeholder="Search"
onChange={(e) => setSearchWord(e.target.value)}
value={searchWord}
/>
<button className="btn btn-outline-success my-2 my-sm-0" type="submit" onClick={() => fetchWord(searchWord)} >Search</button>
</form>
</div>
</div>
</div>
</>
);
}
export default App;
When responseWord was printed the first time, responseWord's value was not updated to new value. Because setState operates asynchronously.
Use useEffect hook instead.
async function fetchWord(word) {
console.log(isLoad)
const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
const data = await res.json();
setIsLoad(true)
setResponseWord(data)
}
useEffect(() => {
console.log(responseWord);
}, [responseWord]);
can you try this one please i think will it help you out
import React, { useEffect, useMemo, useState } from "react";
const App=()=> {
const [searchWord, setSearchWord] = useState("");
const [responseWord, setResponseWord] = useState([]);
const [isLoad, setIsLoad] = useState(false);
const [urlLink, setUrlLink] = useState("");
const fetchWord = async (word) => {
console.log(word);
try {
setIsLoad(false);
const res = await fetch(
`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`
);
const data = await res.json();
setIsLoad(true);
setResponseWord(data);
} catch (er) {
setIsLoad(false);
}
};
const HandleSubmit = (e) => {
e.preventDefault();
fetchWord(searchWord);
};
return (
<>
<Navbar />
<div className="container mt-4">
<div className="row">
<div
className="column bg-success text-light text-center col-3"
style={{ height: "100vh" }}
>
<h4> English Dictionary</h4>
</div>
<div className="column col-5 bg-light">
{isLoad && responseWord.length !== 0 && (
<>
{/* <h3 className="word">{responseWord.word}</h3> */}
{responseWord.map((eg, i) => (
<h3 key={i || eg}>{eg.word}</h3>
))}
</>
)}
</div>
<div className="row col-3" style={{ height: 50 }}>
<form className="d-flex" role="search" onSubmit={HandleSubmit}>
<input
className="form-control mr-sm-2"
placeholder="Search"
onChange={(e) => setSearchWord(e.target.value)}
value={searchWord}
/>
<button
className="btn btn-outline-success my-2 my-sm-0"
type="submit"
>
Search
</button>
</form>
</div>
</div>
</div>
</>
);
}
export default App;
don't console inside the Asynce function cause async function will await until responce came so your results will be previous state
and assign your useState intially with empty array that will work properly if in case empty data
Muhammad, you're off to a great start here. First, let's take a look at your code as it is at the moment. Then, I'll make a couple of recommendations on how to refactor your code.
Congrats! You're actually getting data the first time you click the button and trigger fetchWord function.
You're just calling console.log(responseWord) and console.log(isLoad) too early. You're trying to log responseWord and isLoad right after updating their state within the same function. This happens because "calls to setState are asynchronous inside event handlers" and changes to state variables do NOT reflect the new value immediately after calling setState.
"When state changes, the component responds by re-rendering." And it is in the new re-render that the new state value will be reflected.
Why doesn’t React update state synchronously?
According to React documentation, React intentionally “waits” until all components call setState() in their event handlers before starting to re-render. This boosts performance by avoiding unnecessary re-renders.
When you call setResponseWord("new value") and setIsLoad("new value"), and then try to log the new state values to the console before React re-denders your component, you get false and undefined.
Try console.log(data) instead of console.log(responseWord).
Since you have access to const data = await res.json(); inside your function and before the component re-render happens, you should be able to see your data right away.
The images below ilustrate this example (focus on line 16):
Next, recommendations:
1 . It is recommended to make your AJAX call to an API using useEffect Hook.
This way, you can add the serachWord to the dependency array, and useEffect will execute every time the value of seachWord changes.
In your case, you make your fetch call on button click, but if, for example, you created a web app in which you needed the data to be populated right away without the user having to click a button, useEffect Hook will shine at its best because useEffect automatically runs the side-effect right after initial rendering, and on later renderings only if the value of the variables you passed in the dependency array change.
See the image below from the React documentation to get an idea of how you could refactor your code:
Another recommendation is to get rid of the onClick property in the button, and just let the handleSubmit function call fetchWord (see lines 38 and 21-24).
This information should help you move your app forward. And you're doing great. I see that you're successfully updating state variables, using async/await, making AJAX calls using fetch() and learning React.
Please take a look at the reference list below:
https://reactjs.org/docs/faq-state.html
https://reactjs.org/docs/faq-ajax.html
https://reactjs.org/docs/forms.html

Prevent loosing focus on div element with content editable type

<div
ref={ref}
className={clsx(
classes && classes.size,
error && styles.error,
styles.textarea
)}
data-text={placeholder}
onInput={(e: any) => {
const eventValue = e.target.innerText;
const newValue = wrapper
? formatMessageWithParams(wrapper, { value: eventValue })
: eventValue;
setValue(name, newValue);
}}
contentEditable
id="textarea"
dangerouslySetInnerHTML={{ __html: value }}
/>
I want to make rich text element , which are editable , and will show html string inside .I've tried to find out the ways how to render html string inside of div - the way it can be done with dangerouslySetInnerHtml prop , also I found out that you can not work with onChange , handler which you should use is onInput . What is the problem I have , when I type something , it change value , by setValue function , on each button press, but field loose focus and carret stay on first line . How can I prevent it ? I've tried to make it by ref.current.focus , but it isn't work, div doesn't have autoFocus property , also I've tried to show value by data-value , but html then doesn't work . Is there way to prevent loosing focus on contentEditable div element ?
https://codesandbox.io/s/determined-dawn-i75kg?file=/src/App.js
Rather than using useState for setValues, try to use useRef, it would not re-render the component. And you can also use onKeyDown event to save data.
updated code:
import { useRef } from "react";
import "./styles.css";
export default function App() {
const valueRef = useRef("");
return (
<div className="App">
<div
data-text="write text here"
className="rich-text"
contentEditable
onInput={(e) => {
valueRef.current = e.target.innerHTML;
}}
suppressContentEditableWarning={true}
dangerouslySetInnerHTML={{ __html: valueRef.current }}
/>
</div>
);
}

How to add TextArea while clicking on image using React JS?

###This is how the code looks like ###
const message = () => {
console.log("Hello World!")
}
return(
<label htmlFor="myInput" ><ShoppingBagIcon style={{width:'26px'}} type="shoppingbag" /></label>
<input id="myInput" type="text" style={{display:'none'}} onClick={message} />)}
export default message
You could use a hook const [shown, setShown] = useState(false)
and render the TextArea based on the value of the hook.
An oversimplified example:
const [shown, setShown] = useState(false)
return (
<>
<button on:click={() => setShown(!shown)}/>
{shown ? <TextArea/> : <WithoutTextArea/>}
</>
);
You can use an extra state say, toggleView in your project. And when a user clicks the image you can set the value true or false. Based on that you can show the textField or not. What I meant is
import React,{useState} from 'react'
const message = () => {
const [showText,toggleShowText] = useState(false);
console.log("Hello World!")
const toggleTextField = () => toggleShowText(!showText)
return(
<div>
<label htmlFor="myInput" ><ShoppingBagIcon style={{width:'26px'}} type="shoppingbag" onClick ={()=>toggleShowText(!showText)}/></label>
{
showText ? (
<input id="myInput" type="text" onClick={message} />
) : (<div/>)
}
</div>
);
}
export default message;
Also in your code, the curly braces are not formatted, I did that Above example.
Also, you can use the ternary operator inside the HTML tag.
<input id="myInput" type="text" style={showText ? {display:'none'}: {display:'inline'}} onClick={message} />
or
style={{display: showText ? 'block' : 'none'}}
I Think the best way of doing hiding or showing an HTML element is using ref
import useRef from react
import { useRef } from "react";
Create a ref
const inputRef = useRef();
Use it in the input or textarea
<input
ref={inputRef}
id="myInput"
type="text"
style={{ display: "none" }}
/>
Now you can change it's any style like this
inputRef.current.style.display = "block";
Why ref?
Because it do not re-render the component. If you use state it will cost a render but using ref it won't
here is the solve
Updated
If you want to hide any elements you can do that like this bellow
ref_variable.current.style.display = 'none'
So when and which element you want to hide just make the display none using the ref.
For example if you want to hide the textarea after input. Just do that.

How to change style attribute of element with React Hooks?

Hello I have seen tutorials but not sure how to change element attributes in React like with a button click change the width of an element. This is easy with vanilla javascript using document.querySelector. But how is it done in React?? State? or useRef? It says to avoid refs too much so is there a useState way to do this? Toggling the width is my goal, but any advice is appreciated. I don't think I am supposed to use document.querySelector...
My Attempt:
const myRef = useRef("80%");
const changeWidth = () => {
widthRef.style.width = "100px";
};
<div>
<div style ={{width:400px, height: 400px}} ref ={myRef}>
<button onClick={changeWidth}></button>
</div
you can change style directly using style prop
//
const [width, setWidth] = useState("400px")
function hanldeWidthChange() {
// you can set width to any value you want
setWidth("100px")
}
<div>
<div style ={{width: width, height: "400px"}}>
<button onClick={() => hanldeWidthChange()} ></button>
</div>
</div

Unexpected Behavior After State Change in React Component

RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
console.log(selected)
return(
<div className="results_wrapper">
{selected.map((r,i)=>{
let openState = (this.state.selectedImage==i)?true:false;
return(
<RenderPanel panelType={PanelType.large} openState={openState} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
<div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
<img src={r.url} />
</div>
)} content={(closeIt)=>(
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain,r.parent)}
<div onClick={()=>{
closeIt();
this.setState({selectedImage:2})
console.log('wtfff'+this.state.selectedImage)
}
}>Next</div>
<img src={r.url} />
</div>
)}/>
)
})}
</div>
)
}
When I change the state of 'selectedImage', I expect the variable 'openState' to render differently within my map() function. But it does not do anything.
Console.log shows that the state did successfully change.
And what is even stranger, is if I run "this.setState({selectedImage:2})" within componentsDidMount(), then everything renders exactly as expected.
Why is this not responding to my state change?
Update
I have tried setting openState in my component state variable, but this does not help either:
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
console.log(selected)
let html = selected.map((r,i)=>{
return(
<RenderPanel key={i} panelType={PanelType.large} openState={this.state.openState[i]} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
<div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
<img src={r.url} />
</div>
)} content={(closeIt)=>(
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain,r.parent)}
<div onClick={()=>{
closeIt();
let openState = this.state.openState.map(()=>false)
let index = i+1
openState[index] = true;
this.setState({openState:openState},()=>console.log(this.state.openState[i+1]))
}
}>Next</div>
<img src={r.url} />
</div>
)}/>
)
})
return(
<div className="results_wrapper">
{html}
</div>
)
}
https://codesandbox.io/s/ecstatic-bas-1v3p9?file=/src/Search.tsx
To test, just hit enter at the search box. Then click on 1 of 3 of the results. When you click 'Next', it should close the pane, and open the next one. That is what I'm trying to accomplish here.
#Spitz was on the right path with his answer, though didn't follow through to the full solution.
The issue you are having is that the panel's useBoolean doesn't update it's state based on the openState value passed down.
If you add the following code to panel.tsx, then everything will work as you described:
React.useEffect(()=>{
if(openState){
openPanel()
}else{
dismissPanel();
}
},[openState, openPanel,dismissPanel])
What this is doing is setting up an effect to synchronize the isOpen state in the RenderPanel with the openState that's passed as a prop to the RenderPanel. That way while the panel controls itself for the most part, if the parent changes the openState, it'll update.
Working sandbox
I believe it's because you set openState in your map function, after it has already run. I understand you think the function should rerender and then the loop will run once more, but I think you'll need to set openState in a function outside of render.
The problem is that even though you can access this.state from the component, which is a member of a class component, there's nothing that would make the component re-render. Making components inside other components is an anti-pattern and produces unexpected effects - as you've seen.
The solution here is to either move RenderImages into a separate component altogether and pass required data via props or context, or turn it into a normal function and call it as a function in the parent component's render().
The latter would mean instead of <RenderImages/>, you'd do this.RenderImages(). And also since it's not a component anymore but just a function that returns JSX, I'd probably rename it to renderImages.
I tire to look at it again and again, but couldn't wrap my head around why it wasn't working with any clean approach.
That being said, I was able to make it work with a "hack", that is to explicitly call openIt method for selectedImage after rendering is completed.
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter((x) =>
this.state.selectedGroups.includes(x.domain)
);
return (
<div className="results_wrapper">
{selected.map((r, i) => {
let openState = this.state.selectedImage === i ? true : false;
return (
<RenderPanel
key={i}
panelType={PanelType.medium}
openState={openState}
title={r.domain + ".TheCommonVein.net"}
preview={(openIt) => {
/* This is where I am making explicit call */
if (openState) {
setTimeout(() => openIt());
}
/* changes end */
return (
<div
className="result"
onClick={openIt}
style={{ boxShadow: theme.effects.elevation8 }}
>
<img src={r.url} />
</div>
);
}}
content={(closeIt) => (
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain, r.parent)}
<div
onClick={() => {
closeIt();
this.setState({
selectedImage: i + 1
});
}}
>
[Next>>]
</div>
<img src={r.url} />
</div>
)}
/>
);
})}
</div>
);
};
take a look at this codesandbox.

Resources