React getting state - reactjs

I am currently creating my own mini-bbcode editor for my ReactJS application. I use functional components. In the TextEditor.jsx file I have the following code that handles the textarea.
const [bbcodeText, setBbcodeText] = useState("");
const update = (e) => {
setSelection(null);
setBbcodeText(e.target.value);
if (typeof props.onChange == 'function') {
props.onChange(e.target.value);
}
}
<textarea className="form-input forum__body-input" ref={textAreaRef} value={bbcodeText} onChange={update} />
The above works correctly, however I installed [emoji-mart] 1 to have access to the option to place emojis in the textarea, for which I use the following code:
const handleEmoji = (emoji) => {
let newText = bbcodeText + " " + emoji.native;
setBbcodeText(newText);
}
<Picker title="Emojis" emoji="grinning" set='apple' style={{width: '100%'}} onSelect={handleEmoji} />
The textarea updates correctly, the problem is that this TextEditor component accesses it from a parent component of the form:
const [bbcode, setBBcode] = useState();
const update = (v) => {
setBBcode(v);
};
const handlePost = async() => {
console.log(bbcode)
}
<TextEditor onChange={update} onSelect={handleEmoji} />
<button className="button purple" onClick={handlePost}>Agregar respuesta</button>
The problem occurs if I select an emoji and try to access the "bbcode" state, the result is an empty string, but if I write something after it, the emoji is already shown with the text at the end, that is, I need to fire the onChange to display the updated data.
What I need to know is a guide on how to get the updated status, if I only want to insert emojis in the textarea, for example.
In this case bbcode returns undefined if I only use "emojis" but if I write text it works correctly.

I'd suggest moving the onChange callback logic to an useEffect hook with a dependency on the bbcodeText state. This way anything that updates the bbcodeText state value will trigger the effect to invoke the onChange handler to update anything in the parent component.
const [bbcodeText, setBbcodeText] = useState("");
const update = (e) => {
setSelection(null);
setBbcodeText(e.target.value);
}
const handleEmoji = (emoji) => {
setBbcodeText(bbcodeText => bbcodeText + " " + emoji.native);
}
useEffect(() => {
if (typeof props.onChange == 'function') {
props.onChange(bbcodeText);
}
}, [bbcodeText]);

Related

React with Firestore autocomplete

Using ReactJS, Firestore - Firebase v9.
I have an autocomplete search bar on my web, built with just pure React. The question that I am trying to solve for at least one month is, if I can make this autocomplete input work with Firestore - (user type E.g 'elepha', auto suggestion appears with offer with word elephant - this is what I coded, and with same case, user will click on the suggestion of elephant, and this word elephant will be send to Firestore.)
Cuz there is not any solution on internet, I wonder, if my question is even possible to make, or not.
My simple autocomplete bar code - (animals_data stands for file with animals names)
and I tried to add onClick={pleaseSend} which is basic addDoc function, but when I click on the suggestion, in Firestore will only appear blank input "".
<SearchBar data={animals_data} />
And the filtering code:
function SearchBar({ placeholder, data }) {
const [filteredData, setFilteredData] = useState([]);
const [wordEntered, setWordEntered] = useState("");
const [newAnswer, setAnswer] = useState("")
const [users, setUsers] = useState([]);
const usersCollectionRef = collection(db, "Answers")
const createUser = async () => {
await addDoc(usersCollectionRef, {name: newAnswer}).then(()=>{
window.location.reload()
}).catch((err)=>{
console.log(err)
})
};
const handleFilter = (event) => {
const searchWord = event.target.value;
setWordEntered(searchWord);
const newFilter = data.filter((value) => {
return value.full_name.toLowerCase().includes(searchWord.toLowerCase());
});
if (searchWord === "") {
setFilteredData([]);
} else {
setFilteredData(newFilter);
}
};
const clearInput = () => {
setFilteredData([]);
setWordEntered("");
};
return (
<div className="search">
<div className="searchInputs">
<input
type="text"
placeholder={placeholder}
value={wordEntered}
onChange={handleFilter}
/>
</div>
{filteredData.length !== 0 && (
<div className="dataResult">
{filteredData.slice(0, 15).map((value, key) => {
return (
<a className="dataItem" onClick={createUser} target="_blank">
<p>{value.full_name} </p>
</a>
);
})}
</div>
)}
</div>
);
}
export default SearchBar;
EDIT 1: added code screenshots
web:
Thank you very much for reading.
after analyzing your code, I notice that you don't update the value newAnswer using its setter. Therefore, you should use the setter to update the state on user click and then add the firestorm document. You can do that by either using a button/unmodifiable input field in instead of an anchor tag to store the value of each option, then use this value inside the click handler to update the state and then use a useEffect to update firestore every time the state changes. Let me know if you need help with some code. Please post it below your original question as an edit without changing it.

Difficulty updating state onChange in a Select tag React

I have a simple app to Generate a Random color. But it only updates on the next change and I can't figure out how to get an updater function to work.
const handleHueChange = (e) => {
const newHue = e.target.value;
setHueColor(newHue);
setColor(randomColor({ hue: hueColor, luminosity: lum }));
console.log(color, hueColor, lum);
};
There is the function and here it the select
<form>
<select
name="hue"
value={hueColor.value}
onChange={(e) => handleHueChange(e)}
>
{hue.map((hue, idx) => (
<option key={idx}>{hue}</option>
))}
</select>
</form>
And here is a link to the code sandbox
https://codesandbox.io/s/brave-austin-ujv5xw?file=/src/App.js:1414-1673
The useeffect is giving the right value but it's only updating on the next render.
You are using react 18 and in this version react will batch the changes, which means that react will apply your multiple setState together so you have to use setColor in a useEffect and remove that from handleChanges, something like this:
useEffect(() => {
setColor(randomColor({ hue: hueColor, luminosity: lum }));
}, [hueColor, lum]);
const handleHueChange = (e) => {
const newHue = e.target.value;
setHueColor(newHue);
};
const handleLumChange = (e) => {
const newLum = e.target.value;
setLum(newLum);
};
Inside handleHueChange you are updating hueColor and in next line you were thinking that you can use that new state immediately so you wrote setColor(randomColor({ hue: hueColor, luminosity: lum }));, but hueColor you passed here is still old one because you are reading it from clousure, meaning only after rerender you will be able to access that new state. Solution is to just replace that line with setColor(randomColor({ hue: newHue, luminosity: lum })); - as you can see here I am using newHue instead of hueColor in order to update colors.

State is always one click late

I have a problem with my code, basically setRevalue is always one click late.
const Exchanger = () => {
const [revalue, setRevalue] = useState('null')
const [valueOfInput, setValueOfInput] = useState('');
const [valueOfSelect, setValueOfSelect] = useState('');
const handleClick = () => {
switch (valueOfSelect) {
case 'option_1':
axios.get('http://api.nbp.pl/api/exchangerates/rates/a/chf/?format=json')
.then((response) => {
setRevalue(response.data.rates[0].mid)
console.log(revalue);
})
break;
const handleInputChange = (event) => {
setValueOfInput(event.target.value);
}
const handleSelectChange = (event) => {
setValueOfSelect(event.target.value);
}
return(
<>
<Input
labelText= 'Amount'
onChange= { handleInputChange }
value = { valueOfInput }
/>
<Select selectValue={ valueOfSelect } onSelectChange={ handleSelectChange } />
<Button onClick={handleClick}>Klik!</Button>
</>
)
On the first click it returns a null which it was supposed to be before the click.
Any help would be much appreciated.
Hey so you have a clousure issue there
In JavaScript, closures are created every time a function is created, at function creation time.
At the time the .then() function is created, revalue has the value of the last render, That's why even after updating the state with setRevalue (and thus triggering a rerender), you get the "old value" (the one corresponding to the render in which the .then function was created).
I don't know exactly your use case, but seems like you can achieve whatever you are trying to achieve by doing:
const revalue = response.data.rates[0].mid
setRevalue(revalue)
console.log(revalue);
if you want to access to revalue right after setting it you have to use useEffect with revalue as a parameter , it would be like this
useEffect(()=>{
console.log(revalue);
},[revalue])
now every time revalue change you will have current result

state not changing on onChange event in react?

This is my code:
function AddPost() {
const [file, setFile] = useState({})
const handleChange = (event) => {
setFile(event.target.files[0]);
console.log(file);
}
return (
<div>
<TextField type='file' onChange={handleChange} label='image' variant='outlined' />
</div>
)
}
I am not getting file info. on console, while i am selecting a file . Instead of that I am getting empty object why ?
You need to use a useEffect to besure you are doing your action after a state is updated. Here is an example :
import React, { Component } from "react";
import { render } from "react-dom";
const App = () => {
const [num, setNum] = React.useState(0);
const handleClick = () => {
setNum(1);
console.log('num =', num);
}
// Note the dependency array below
React.useEffect(() => console.log('num (useEffect) = ', num), [num]);
return (
<div>
<button onClick={handleClick}>Click</button>
</div>
);
};
render(<App />, document.getElementById("root"));
and here is repro on Stackblitz.
Here, on click, num will be 0 in the function, but it will be set to 1 in the useEffect.
Just the file is not updated yet (whitin onChange) , so you see the initial state
The state update is performed asynchronously and only visible in the next render call.
In the next render call, file will be updated to the new value and then your (also new) function handleChange will use that value.
This means in your current code the log will always be off by one (empty object, file 1, file 2 etc.) when uploading one file after each other.
The behavior is described in the documentation of the useState hook.
To fix the log, simply write
const newFile = event.target.files[0];
setFile(newFile);
console.log(newFile);

React - UseEffect not re-rendering with new data?

This is my React Hook:
function Student(props){
const [open, setOpen] = useState(false);
const [tags, setTags] = useState([]);
useEffect(()=>{
let input = document.getElementById(tagBar);
input.addEventListener("keyup", function(event) {
if (event.keyCode === 13) {
event.preventDefault();
document.getElementById(tagButton).click();
}
});
},[tags])
const handleClick = () => {
setOpen(!open);
};
function addTag(){
let input = document.getElementById(tagBar);
let tagList = tags;
tagList.push(input.value);
console.log("tag");
console.log(tags);
console.log("taglist");
console.log(tagList);
setTags(tagList);
}
const tagDisplay = tags.map(t => {
return <p>{t}</p>;
})
return(
<div className="tags">
<div>
{tagDisplay}
</div>
<input type='text' id={tagBar} className="tagBar" placeholder="Add a Tag"/>
<button type="submit" id={tagButton} className="hiddenButton" onClick={addTag}></button>
<div>
);
What I am looking to do is be able to add a tag to these student elements (i have multiple but each are independent of each other) and for the added tag to show up in the tag section of my display. I also need this action to be triggerable by hitting enter on the input field.
For reasons I am not sure of, I have to put the enter binding inside useEffect (probably because the input element has not yet been rendered).
Right now when I hit enter with text in the input field, it properly updates the tags/tagList variable, seen through the console.logs however, even though I set tags to be the re-rendering condition in useEffect (and the fact that it is also 1 of my states), my page is not updating with the added tags
You are correct, the element doesn't exist on first render, which is why useEffect can be handy. As to why its not re-rendering, you are passing in tags as a dependency to check for re-render. The problem is, tags is an array, which means it compares the memory reference not the contents.
var myRay = [];
var anotherRay = myRay;
var isSame = myRay === anotherRay; // TRUE
myRay.push('new value');
var isStillSame = myRay === anotherRay; // TRUE
// setTags(sameTagListWithNewElementPushed)
// React says, no change detected, same memory reference, skip
Since your add tag method is pushing new elements into the same array reference, useEffect thinks its the same array and is not re-triggers. On top of that, React will only re-render when its props change, state changes, or a forced re-render is requested. In your case, you aren't changing state. Try this:
function addTag(){
let input = document.getElementById(tagBar);
let tagList = tags;
// Create a new array reference with the same contents
// plus the new input value added at the end
setTags([...tagList, input.value]);
}
If you don't want to use useEffect I believe you can also use useRef to get access to a node when its created. Or you can put the callback directly on the node itself with onKeyDown or onKeyPress
I can find few mistake in your code. First, you attaching event listeners by yourself which is not preferred in react. From the other side if you really need to add listener to DOM inside useEffect you should also clean after you, without that, another's listeners will be added when component re-rendered.
useEffect( () => {
const handleOnKeyDown = ( e ) => { /* code */ }
const element = document.getElementById("example")
element.addEventListener( "keydown", handleOnKeyDown )
return () => element.removeEventListener( "keydown", handleOnKeyDown ) // cleaning after effect
}, [tags])
Better way of handling events with React is by use Synthetic events and components props.
const handleOnKeyDown = event => {
/* code */
}
return (
<input onKeyDown={ handleOnKeyDown } />
)
Second thing is that each React component should have unique key. Without it, React may have trouble rendering the child list correctly and rendering all of them, which can have a bad performance impact with large lists or list items with many children. Be default this key isn't set when you use map so you should take care about this by yourself.
tags.map( (tag, index) => {
return <p key={index}>{tag}</p>;
})
Third, when you trying to add tag you again querying DOM without using react syntax. Also you updating your current state basing on previous version which can causing problems because setState is asynchronous function and sometimes can not update state immediately.
const addTag = newTag => {
setState( prevState => [ ...prevState, ...newTage ] ) // when you want to update state with previous version you should pass callback which always get correct version of state as parameter
}
I hope this review can help you with understanding React.
function Student(props) {
const [tags, setTags] = useState([]);
const [inputValue, setInputValue] = useState("");
const handleOnKeyDown = (e) => {
if (e.keyCode === 13) {
e.preventDefault();
addTag();
}
};
function addTag() {
setTags((prev) => [...prev, inputValue]);
setInputValue("");
}
return (
<div className="tags">
<div>
{tags.map((tag, index) => (
<p key={index}>{tag}</p>
))}
</div>
<input
type="text"
onKeyDown={handleOnKeyDown}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Add a Tag"
/>
<button type="submit" onClick={addTag}>
ADD
</button>
</div>
);
}

Resources