I have two input fields and allowing user to type on either inputs to perform meters to kilometers conversion. I'm trying to use useEffect to watch for changes and update either input field but it's not functioning correctly.
import React, { useState, useEffect } from "react";
import './App.css';
function App() {
const [value, setValue] = useState('');
const [value2, setValue2] = useState('');
useEffect(() => {
let res = parseInt(event.target.value) * 1000;
setValue2(res.toString());
}, [value])
useEffect(() => {
let res = parseInt(event.target.value) / 1000;
setValue(res.toString());
}, [value2])
const onChange = (event) => {
setValue(event.target.value);
};
const onChange2 = (event) => {
setValue2(event.target.value);
};
return (
<>
<div>Meters</div>
<input value={value} onChange={onChange} />
<div>Kilometers</div>
<input value={value2} onChange={onChange2} />
</>
);
}
export default App;
you don't need useEffect to achieve what you want :
import React, { useState } from "react";
function App() {
const [meters, setMeters] = useState(null);
const [kilometers, setKilometers] = useState(null);
const handleMeterInput = (event) => {
setMeters(event.target.value);
setKilometers(event.target.value / 1000);
};
const handleKiloInput = (event) => {
setKilometers(event.target.value);
setMeters(event.target.value * 1000);
};
return (
<>
<h2> Meters to Kilometers converter </h2>
<div>
<strong>Meters :</strong>
<input type="number" value={meters} onChange={handleMeterInput} />
</div>
<div>
<strong>Kilometers :</strong>
<input type="number" value={kilometers} onChange={handleKiloInput} />
</div>
</>
);
}
export default App;
sandbox example here
Related
I am trying to create a debounce search and initially when the field is empty the component renders after the provided setTimeout delay. But if I continue to search with the existing keyword it re-renders the List component on each key stroke. How to avoid that?
import { useEffect, useState } from 'react';
import useDebounce from './hooks/useDebounce';
import List from './components/List';
const App: React.FC = () => {
const [todo, setTodo] = useState<string>("");
const [query, setQuery] = useState<string | null>("");
let deBounceSearch = useDebounce(query, 2000);
useEffect(() => {
if (deBounceSearch) {
console.log('Searching...');
} else {
console.log('...');
}
}, [deBounceSearch]);
return (
<div className="App">
<input type="text" placeholder='Search anything' onChange={(e) => setQuery(e.target.value)} />
{deBounceSearch !== '' && (
<List />
)}
</div>
);
}
useDebounce.tsx
const useDebounce = (value: any, delay: number) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => { setDebouncedValue(value) }, delay);
return () => {
clearTimeout(handler);
}
}, [value, delay]);
return debouncedValue;
}
export default useDebounce;
You can use useMemo to avoid re-render the List component every time query value changes:
const App: React.FC = () => {
const [todo, setTodo] = useState<string>("");
const [query, setQuery] = useState<string | null>("");
const deBounceSearch = useDebounce(query, 2000);
// ->
const cachedList = React.useMemo(() => <List />, [debouncedValue]);
...
return (
<div className="App">
<input type="text" placeholder='Search anything' onChange={(e) => setQuery(e.target.value)} />
{deBounceSearch !== '' && cachedList}
</div>
);
}
You also can take a look at React.memo
hello man this is not the best to use debounce i suggest u try lodash debounce with useMemo.
but for now the solution for your code is that you forgot to clear the timeout on every time the value change.
here the solution:
import { useEffect, useState, useRef } from "react";
import "./styles.css";
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
const handler = useRef();
useEffect(() => {
if (handler.current) {
clearTimeout(handler.current);
}
handler.current = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler.current);
};
}, [value, delay]);
return debouncedValue;
};
export default function App() {
const [value, setValue] = useState("");
const debounceSearch = useDebounce(value, 2000);
console.log(debounceSearch);
return (
<div className="App">
<input value={value} onChange={(e) => setValue(e.target.value)} />
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
hope this help .
So I have a context.
import React from "react";
const CartContext = React.createContext({
numberOfMeals: 0,
meals: [],
});
export default CartContext;
I then import this into my App.JS
import React from "react";
import MealsBase from "./Components/Meals/MealsBase";
import CartContext from "./Context/CartContext";
function App() {
return (
<CartContext.Provider value={{ numberOfMeals: 0, meals: [] }}>
<MealsBase/>
</CartContext.Provider>
);
}
export default App;
I use this context within two components.
Component 1 :
import React, {useContext} from "react";
import "./CartIcon.css";
import CartContext from "../../Context/CartContext";
const CartIcon = () => {
const cartContext = useContext(CartContext);
return (
<React.Fragment>
<button className="border">
<h3>Your Cart : {cartContext.numberOfMeals}</h3>
</button>
</React.Fragment>
);
};
export default CartIcon;
Component 2 :
import React,{ useState, useContext } from "react";
import CartContext from "../../Context/CartContext";
const MealForm = () => {
const [amount, setAmount] = useState(0);
const mealContext = useContext(CartContext);
const amountHandler = (event) => {
setAmount(event.target.valueAsNumber);
};
const formHandler = (event) => {
mealContext.numberOfMeals = mealContext.numberOfMeals + amount;
console.log(mealContext.numberOfMeals);
event.preventDefault();
}
return(
<React.Fragment>
<form onSubmit={formHandler}>
<label>Amount</label>
<input type="number" step={1} min={-1} max={50} value={amount} onChange={amountHandler}/>
<button type="submit"> + Add </button>
</form>
</React.Fragment>
);
};
export default MealForm
The context seems to update in component two but not in component one.
I have an understanding that if useContexts values change then it causes a component to re-render. So I am struggling to understand why the CartIcon.js file is not being re-rendered.
You aren't really updating the value the React way, you are mutating it. Move the context value into state and provide that out.
const CartContext = React.createContext({
numberOfMeals: 0,
setNumberOfMeals: () => {},
meals: [],
setMeals: () => {},
});
...
function App() {
const [numberOfMeals, setNumberOfMeals] = useState(0);
const [meals, setMeals] = useState([]);
return (
<CartContext.Provider value={{ numberOfMeals, setNumberOfMeals, meals, setMeals }}>
<MealsBase/>
</CartContext.Provider>
);
}
...
const MealForm = () => {
const [amount, setAmount] = useState(0);
const { setNumberOfMeals } = useContext(CartContext);
const amountHandler = (event) => {
setAmount(event.target.valueAsNumber);
};
const formHandler = (event) => {
event.preventDefault();
setNumberOfMeals(count => count + amount);
}
return(
<React.Fragment>
<form onSubmit={formHandler}>
<label>Amount</label>
<input type="number" step={1} min={-1} max={50} value={amount} onChange={amountHandler}/>
<button type="submit"> + Add </button>
</form>
</React.Fragment>
);
};
I want to create a custom hook useComponent which returns a JSX.Element that will be rendered elsewhere.
I have tried this:
import { useState} from 'react';
const useComponent = () => {
const [value, setValue] = useState('');
const c = () => {
return <>
<p>Component</p>
<input value={value} onChane={(e) => setValue(e.target.value)} />
</>
}
return {
c,
value,
}
}
export default function App() {
const {c: C} = useComponent();
return (
<div className="App">
<C />
</div>
);
}
but it does not work. Once I try typing on input, nothing happens.
How can I achieve this ?
I know it might be a bad practice to do such a thing, but the reason I want this is to be able to open a global dialog and pass the c component as children to the <Dialog /> component so I can both render c inside the dialog's body and also have access to the [value, setValue] state. So my use case would be something like:
[EDIT]
I also add the whole logic with dialog:
import { createContext, useContext, useState } from "react";
const Test = ({ value, setValue }) => {
return (
<>
<p>Component</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
};
const useComponent = () => {
const [value, setValue] = useState("");
return {
element: <Test value={value} setValue={setValue} />,
value
};
};
const DialogCTX = createContext({});
export function DialogProvider(props) {
const [component, setComponent] = useState(null);
const ctx = {
component,
setComponent
};
return (
<DialogCTX.Provider value={ ctx }>
{props.children}
</DialogCTX.Provider>
);
}
export const useDialog = () => {
const {
component,
setComponent,
} = useContext(DialogCTX);
return {
component,
setComponent,
}
};
const Dialog = () => {
const { component } = useDialog();
return <div>
<p>Dialog</p>
{component}
</div>
}
const Setter = () => {
const {element, value} = useComponent();
const {setComponent} = useDialog();
return <div>
<p>Setter component</p>
<p>{value}</p>
<button onClick={() => setComponent(element)}>Set</button>
</div>
}
export default function App() {
return <div className="App">
<DialogProvider>
<Setter />
<Dialog />
</DialogProvider>
</div>;
}
As you said you want to return a JSX.Element but you actually returning a new component (a new function) every time your hook runs. So you could achieve your goal if you actually declare your component outside your hook and return the rendered one. Here is a working example:
import { useState } from "react";
const Test = ({ value, setValue }) => {
return (
<>
<p>Component</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
};
const useComponent = () => {
const [value, setValue] = useState("");
return {
element: <Test value={value} setValue={setValue} />,
value
};
};
export default function App() {
const { element } = useComponent();
return <div className="App">{element}</div>;
}
I'm new to react.
Now trying to make form with react hooks, and I want to render Cloud component only when press submit button. But it rendered every onChange called.
I know that onChange re-rendered cause also useState hook.
But have no idea how to render only when press submit button.
My final goal is when write name and press enter, if value is not contained in api, setShake make shake True and if True, put shake-cloud class in Cloud.js.
REACT IS TOO DIFFICULT :(
Thanks for help tho :)
App.js
import React, { useState, useEffect } from "react";
import "./App.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch } from "#fortawesome/free-solid-svg-icons";
import "./search.css";
import PageTitle from "./component/PageTitle";
import Cloud from "./component/Cloud";
import Loading from "./component/Loading";
//https://api.color.pizza/v1/
//data.colors[0].name
const App = () => {
const [isLoading, setIsLoading] = useState(false);
const [colorNames, setColorNames] = useState("");
const [search, setSearch] = useState("");
const [query, setQuery] = useState("");
const [cloudHex, setCloudHex] = useState("ivory");
const [shake, setShake] = useState(false);
useEffect(() => {
getColorLists();
}, []);
const getColorLists = async () => {
const res = await fetch(`https://api.color.pizza/v1/`);
const data = await res.json();
await setColorNames(data);
setIsLoading(true);
};
const isColor = () => {
let makeUpper =
query.search(/\s/) == -1
? query.charAt(0).toUpperCase() + query.slice(1)
: query
.split(" ")
.map((i) => i.charAt(0).toUpperCase() + i.slice(1))
.join(" ");
for (let i = 0; i < colorNames.colors.length; i++) {
if (colorNames.colors[i].name == makeUpper) {
setCloudHex(colorNames.colors[i].hex);
return;
} else if (i == colorNames.colors.length - 1) {
return makeShake();
}
}
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setQuery(search);
isColor();
};
const makeShake = async () => {
await setShake(true)
await setShake(false)
}
return (
<>
{!isLoading ? (
<Loading />
) : (
<div className="App">
<div className="app-wrap">
<PageTitle />
<div className="search-wrap">
<form onSubmit={getSearch} className="search-form">
<input
className="search-bar"
type="text"
value={search}
onChange={updateSearch}
/>
<button type="submit" className="search-button">
<FontAwesomeIcon
icon={faSearch}
className="search"
/>
</button>
</form>
</div>
<Cloud cloudhex={cloudHex} shake={shake} />
</div>
</div>
)}
</>
);
};
export default App;
Cloud.js
import React, {useEffect} from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCloud } from "#fortawesome/free-solid-svg-icons";
import './cloud.css';
const Cloud = ({cloudhex, shake}) => {
useEffect(() => {
}, [])
console.log(shake)
return (
<div className={`cloud-wrap ${ shake ? "shake-cloud":''}`}>
<span className="cloudhexname">{cloudhex}</span>
<FontAwesomeIcon icon={faCloud} className="cloud" style={{color:`${cloudhex}`}} />
</div>
);
};
export default Cloud;
A good approach in this case is to use useRef() Hook to store our search field value, instead of using useState(). Because useRef() Hook does not force a re-render while useState() does. This approach is known as un-controlled way to use input field.
You basically need to make few modifications in your code which are as follows:
const search = useRef("");
Then remove onChange={updateSearch} and value={search} from input and use a property ref={search}. So that your input looks like below:
<input
className="search-bar"
type="text"
ref={search}
/>
Then in the submit handler, you can get the value of the input field using search.current.value. So your getSearch() would look like
const getSearch = (e) => {
e.preventDefault();
setClicked(true);
setQuery(search.current.value);
isColor();
};
Assuming user has typed an input. If not then you can set a validation before using setQuery() in the getSearch() form submit handler.
if(search.current.value){
setQuery();
}
Note: If you have any other controlled inputs in your project, you can change then to un-controlled inputs using refs and this way re-renders would not happen in your code.
Do it like that
If you want to render cloud component after form submit then put one flag and toggle that, here I take clicked state
import React, { useState, useEffect } from "react";
import "./App.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch } from "#fortawesome/free-solid-svg-icons";
import "./search.css";
import PageTitle from "./component/PageTitle";
import Cloud from "./component/Cloud";
import Loading from "./component/Loading";
//https://api.color.pizza/v1/
//data.colors[0].name
const App = () => {
const [isLoading, setIsLoading] = useState(false);
const [colorNames, setColorNames] = useState("");
const [search, setSearch] = useState("");
const [query, setQuery] = useState("");
const [cloudHex, setCloudHex] = useState("ivory");
const [shake, setShake] = useState(false);
const [clicked, setClicked] = useState(false);
useEffect(() => {
getColorLists();
}, []);
const getColorLists = async () => {
const res = await fetch(`https://api.color.pizza/v1/`);
const data = await res.json();
await setColorNames(data);
setIsLoading(true);
};
const isColor = () => {
let makeUpper =
query.search(/\s/) == -1
? query.charAt(0).toUpperCase() + query.slice(1)
: query
.split(" ")
.map((i) => i.charAt(0).toUpperCase() + i.slice(1))
.join(" ");
for (let i = 0; i < colorNames.colors.length; i++) {
if (colorNames.colors[i].name == makeUpper) {
setCloudHex(colorNames.colors[i].hex);
return;
} else if (i == colorNames.colors.length - 1) {
return makeShake();
}
}
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setClicked(true);
setQuery(search);
isColor();
};
const makeShake = async () => {
await setShake(true)
await setShake(false)
}
return (
<>
{!isLoading ? (
<Loading />
) : (
<div className="App">
<div className="app-wrap">
<PageTitle />
<div className="search-wrap">
<form onSubmit={getSearch} className="search-form">
<input
className="search-bar"
type="text"
value={search}
onChange={updateSearch}
/>
<button type="submit" className="search-button">
<FontAwesomeIcon
icon={faSearch}
className="search"
/>
</button>
</form>
</div>
{clicked && <Cloud cloudhex={cloudHex} shake={shake} />}
</div>
</div>
)}
</>
);
};
export default App;
I have a component Search component with suggestions. I create the suggestions by javascript. I can print all the suggestions, but how I can select the suggest element to put into the placeholder? Thanks for your response.
SEARCH COMPONENT
import React, { useEffect, useState } from "react";
import api from "../../api/index";
import "./index.css";
export default function Search(query) {
const [searchSchool, setSearchShool] = useState("");
const [showSuggestions, setShowSuggestions] = useState(false);
const [allSchools, setAllSchools] = useState([]);
const inputRef = React.useRef(null);
useEffect(() => {
(async () => {
const data = await api.getSchools();
setAllSchools(data);
})();
}, []);
const resultsAllSchools = !searchSchool
? allSchools
: allSchools.filter(school => school.toLowerCase().includes(searchSchool));
const handleOnclick = e => {
e.preventDefault();
const lowerCase = e.target.value.toLowerCase();
setSearchShool(lowerCase);
setShowSuggestions(true);
if (e.target.value === "" || null) {
setShowSuggestions(false);
}
};
return (
<>
<form>
<input
className="select"
type="text"
placeholder="Cerca el teu centre educatiu"
value={searchSchool}
ref={inputRef}
onChange={handleOnclick}
></input>
<button>
</button>
<ul>
{showSuggestions &&
resultsAllSchools.map((item, i) => <li key={i}>{item}</li>)}
</ul>
</form>
</>
);
}