React event listeners with useEffect - javascript

import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [theRoom, setRoom] = useState(null);
const updateVideoDevice = (e) => {
console.log("room", theRoom);
};
const createRoom = () => {
console.log("we change the room", theRoom);
setRoom({
localparticipants: {}
});
console.log("we change the room after", theRoom);
};
useEffect(() => {
const select = document.getElementById("video-devices");
select.addEventListener("change", updateVideoDevice);
}, []);
return (
<div className="App">
<select id="video-devices">
<option value="1">1</option>
<option value="2">2</option>
</select>
<button onClick={createRoom}>change obj</button>
</div>
);
}
I have this codebase. When I press the change obj button for the first time it doesn't set theRoom to the object
{
localparticipants: {}
}
But when I press the button for the second time it does, and after that, I try to change the select element's options I got null for the console log in the updateVideoDevice function.
How do I solve these two issues with React?

const [theRoom, setRoom] = useState(null);
setRoom is asynchronous function, so you will not see the change immediately.
Also, you don't need to add event listener to select tag. You can simply use onChange method. So remove all with useEffect and then change the code as below.
<select id="video-devices" onChange={updateVideoDevice}>

you need to chnage how you call the onclick method so it's look like this:
<button onClick={()=>createRoom()}>change obj</button>

Here's a working solution:
setRoom is asynchronous so your 2 console.log(theRoom) can't show the right value while you're still in the function.
to handle the select change value, you can use the onChange React prop that allows you to catch every value change of a hook
I put you 2 console.login a useEffect to allow you to see the changes of the theRoom and selectValue values
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [theRoom, setRoom] = useState(null);
const [selectValue, setSelectValue] = useState(null);
const createRoom = () => {
setRoom({
localparticipants: {}
});
};
// everytime selectValue or theRoom values changed, this trigger this
useEffect(() => {
console.log(selectValue);
console.log(theRoom);
}, [selectValue, theRoom]);
return (
<div className="App">
<select
id="video-devices"
onChange={(e) => setSelectValue(e.target.value)}
value={selectValue}
>
<option value="1">1</option>
<option value="2">2</option>
</select>
<button onClick={createRoom}>change obj</button>
</div>
);
}

Related

How to capture when dropdown is open on React

Is there a way to tell when the dropdown is open and also closed? onfocus and onblur doesn't seem to be working.
<div className="select-container">
<select>
{options.map((option) => (
<option value={option.value}>{option.label}</option>
))}
</select>
</div
You should use useState to keep track of the dropdown status. It would look something like this:
import "./styles.css";
import { useState } from "react";
export default function App() {
const [isDropDownOpen, setDropDownOpen] = useState(false);
let options = [
{
label: "money"
}
];
const handleSelect = () => {
setDropDownOpen(!isDropDownOpen);
};
const handleBlur = () => {
setDropDownOpen(!isDropDownOpen);
};
console.log(isDropDownOpen);
return (
<div>
<select onBlur={handleBlur} onClick={handleSelect}>
{options.map((option) => (
<option value={option.value}>{option.label}</option>
))}
</select>
</div>
);
}
I have tied it into the handleSelect function, which will probably do more than just keep track of whether or not the dropdown is open but either way, it works as a reference point for now.
EDIT: Also, in case you click outside the dropdown, I used onBlur, which is controlled by handleBlur to change the boolean value because obviously, the dropdown will close.
Check the console.log on the this code sandbox to see how it works: https://codesandbox.io/s/amazing-easley-0mf7o3?file=/src/App.js

Adding data to array using UseState onChange

I am having some issues figuring out how I can get the state of an inputfield, and add it to an useState array.
The way this code is set up, using onChange, it will add every character I type in the textField as a new part of the array, but I dont want to set the value until the user is done typing.
What would be a simple solution to this?
My code:
const [subject, setSubject] = useState([]);`
<input type="text" placeholder={"Eks. 'some example'"} onChange={(e) => setSubject(oldArray => [...oldArray, e.target.value])}/>
Well, I am not confident with react yet, but unless you don't want to do some validation, why don't you use useRef hook and onBlur combination. UseRef hook basically set a reference on element and then you can use value from that reference which in your case would be textField value. OnBlur will trigger when user clicks outside of input (input lose focus) Code should look like this:
import react, {useRef, useState} from "react";
const someComponent = (props) => {
const [subject, setSubject] = useState([]);
const textAreaRef = useRef();
const onBlurHandler = () => {
setSubject((prevSubject) => [...prevSubject, textAreaRef.current.value]);
}
return <input type="text" placeholder={"Eks. 'some example'"} ref={textAreaRef} onBlur={onBlurHandler}/>
}
Other way would be to use debouncing with useEffet.
this is a little something i cooked up for you... it watches the change of the input, and 1 second after the person stops typing, it will add the input value.
The main things to look at here are the useEffect() and the <input /> with the new state i made [input, setInput]. or you can play around with this here
export default function App() {
const [subjects,setSubjects] = useState([]);
const [input,setInput] = useState("")
useEffect(() => {
const timer = setTimeout(() => {
setSubjects(old => [...old, input])
}, 1000)
return () => clearTimeout(timer)
}, [input])
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<input placeholder="type here"
value={input}
type="text"
onChange={e => setInput(e.target.value)}
/>
{subjects.length === 0 ?
<h3>Nothing yet...</h3>
:
<h3>{subjects}</h3>
}
</div>
);
}

React testing: Change select value in test

I am trying to write a test to check a value after a change in a <select> dropdown. This is my component:
import React from 'react';
const BasicDropdown = ({ key, value, options }) => {
return (
<div className={`basic-dropdown-${key}`}>
<select
className={`basic-dropdown-${key}-select`}
name={`basic-dropdown-${key}-select`}
{...{value}}
>
{options.map(option =>
<option
className={`basic-dropdown-${key}-select-${option}-option`}
key={option}
value={option}
>
{option.charAt(0).toUpperCase() + option.slice(1)}
</option>
)}
</select>
</div>
);
};
export default BasicDropdown;
So far it's very simple. The reason for having a component is that I will expand this later depending on the props and other things. So I decided to write a test for this component to start with:
import React from 'react'
import TestUtils from 'react-dom/test-utils';
import BasicDropdown from './BasicDropdown';
const options = ['option-A', 'option-B', 'option-C'];
describe("BasicDropdown", () => {
let renderedCmpt;
beforeAll(() => {
renderedCmpt = TestUtils.renderIntoDocument(
<BasicDropdown key="test" value="option-A" options={options} />
)
});
it("Should have correct value after change", () => {
const select = TestUtils.findRenderedDOMComponentWithClass(renderedCmpt, 'basic-dropdown-test-select');
expect(select.value).toEqual("option-A");
TestUtils.Simulate.change(select, {target: {value: 'option-C'}});
const selectChanged = TestUtils.findRenderedDOMComponentWithClass(renderedCmpt, 'basic-dropdown-test-select');
expect(selectChanged.value).toEqual("option-C");
});
});
My problem is that when running this test using jest (jest --coverage=false --config ~/projects/Beehive/config/jest.config.js ~/projects/Beehive/tests/BasicDropdown-test.js) I get the following error:
Expected: "option-C"
Received: "option-A"
at line 21, which means that the value of the select is never changed during the Simulate.
What am I doing wrong?
You need to add onChange on select element to reflect the change on the test, else it will always be option-A.

React changing input value to another input value using if condition error

I am using react and I am trying to change the value of an input to another input using an if conditon. I am encountering an error where the input value changes to the previous value and not the current one. Example: I enter 1 in input1 and input2 stays blank. Then I add a 0 to input1 making the value 10, the second input then changes its value to 1. I then delete the 0 from input 1 making the value once again 1 and the second input changes to 10.
I am using useState here is my code:
import React, { useState } from 'react'
export default function () {
const [select1, setSelect1] = useState("test")
const [input1, setInput1] = useState("")
const [input2, setInput2] = useState("")
function select1Function(e) {
setInput1(e.target.value)
if (select1 === "test") {
setInput2(input1)
document.getElementById("input2").disabled = true
} else {
document.getElementById("input2").disabled = false
}
}
return (
<div>
<select value={select1} onChange={e => setSelect1(e.target.value)}>
<option value="test">Test</option>
<more options here (irrelevant to question)>
</select>
<input type="number" value={input1} onChange={select1Function}></input>
<input type="number" value={input2} onChange={e => setInput2(e.target.value)} id="input2"></input>
</div>
)
}
The value of input2 is on a one character delay and I cant figure out why. I would appreciate any help and thanks in advance.
A call to setState isn't synchronous. It creates a "pending state transition." (See here for more details). You should explicitly pass the new input value as part of the event being raised.
Your code should be:
import React, { useState } from 'react'
export default function () {
const [select1, setSelect1] = useState("test")
const [input1, setInput1] = useState("")
const [input2, setInput2] = useState("")
function select1Function(e) {
setInput1(e.target.value)
if (select1 === "test") {
setInput2(e.target.value)
document.getElementById("input2").disabled = true
} else {
document.getElementById("input2").disabled = false
}
}
return (
<div>
<select value={select1} onChange={e => setSelect1(e.target.value)}>
<option value="test">Test</option>
<more options here (irrelevant to question)>
</select>
<input type="number" value={input1} onChange={select1Function}></input>
<input type="number" value={input2} onChange={e => setInput2(e.target.value)} id="input2"></input>
</div>
)
}

Update a component with onChange. React-Hooks

I'm building a dropdown with suggestions that fetch data from an API. The input from the search bar is being stored using setState and it is updated when i change the value in the text input.
The thing is that I'm not managing to update the users lists from the dropdown each time I enter a new character in the text input. Can I somehow force the component to be rendered every time the props change? Or is there a better approach?
import React, {useState, useEffect} from 'react';
import Dropdown from '../Dropdown/Dropdown';
import './SearchBar.css';
// Component created with arrow function making use of hooks
const SearchBar = (props) => {
const [input, setInput] = useState('');
const [dropdownComponent, updateDropdown] = useState(<Dropdown input={input}/>)
useEffect(() => {updateDropdown(<Dropdown input={input}/>)}, [input])
const onChange = (e) => {
setInput(e.currentTarget.value)
updateDropdown(<Dropdown input={input}/>)
console.log("=(")
}
return(
<div className="search-bar">
<input type="text" placeholder={props.placeholder} onChange={onChange}/>
{dropdownComponent}
</div>
)
}
export default SearchBar;
I can't make the problem happen using your code in a simple test, but your onChange does has a problem: It's using input to update the dropdown, but it's not using useCallback to ensure that input isn't stale when it does. Either:
Don't update the dropdown in your onChange, allowing your useEffect callback to do it; or
Use e.target.value instead of input and get rid of the useEffect updating the dropdown; or
Don't memoize the dropdown (e.g., don't put it in state) since you want to update it when the input changes anyway, just render it directly in the JSX
Of those, with what you've shown, #3 is probably the simplest:
const SearchBar = (props) => {
const [input, setInput] = useState('');
const onChange = (e) => {
setInput(e.currentTarget.value);
};
return(
<div className="search-bar">
<input type="text" placeholder={props.placeholder} onChange={onChange}/>
<Dropdown input={input}/>
</div>
);
}
Live Example:
const {useState, useEffect} = React;
function Dropdown({input}) {
return <div>Dropdown for "{input}"</div>;
}
const SearchBar = (props) => {
const [input, setInput] = useState('');
const onChange = (e) => {
setInput(e.currentTarget.value);
};
return(
<div className="search-bar">
<input type="text" placeholder={props.placeholder} onChange={onChange}/>
<Dropdown input={input}/>
</div>
);
}
ReactDOM.render(<SearchBar />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

Categories

Resources