interface MyValue {
//interface declaration
}
export function MyComponent {
const [myvalue, setMyvalue] = useState<MyValue>()
useEffect(() => {
setMyvalue(passedData)
}, [passedData])
function getAutofocus() {
// return true/false based on myvalue value
}
render() {
return (
<div>
<input
autofocus={getAutofocus()}
ref={c => (this._input = c)}
/>
</div>
);
}
}
}
passedData is passed as a prop from parent and this is populated in the parent via a server GET call, which takes some time to resolve.
PROBLEM - getAutofocus() rendered before passedData is properly loaded.
my requirement here is to wait until passedData is properly resolved before invoking the
getAutofocus() method.
If we can stop the rendering of the UI/ or the input field until passedData is completely resolved, that will allow getAutofocus() to properly execute.
what's the best way to do this? can react suspense be used here?
Sounds like conditional rendering would be enough to get what you need:
render() {
// if myvalue is not populated yet, do not render anything
return !myvalue ? null : (
<div>
<input
autofocus={getAutofocus()}
ref={c => (this._input = c)}
/>
</div>
);
}
}
The proper way of doing this is with a ref
const MyCom = props => {
const inputRef = React.useRef();
React.useEffect(()=>{
if (inputRef.current) {
inputRef.current.focus();
}
},[inputRef]);
return (
<div>
<input
ref={inputRef}
/>
</div>
);
}
Remove render method only class components have render
Related
How do you update the LatestTweetsComponent with the data returned from a fetch call that happens in handleRequest? tweets is updating correctly onClick however the LatestTweetsComponent does not render or update. I may be approaching this incorrectly.
const LatestTweets = () => {
const [tweets, setTweets] = useState(null)
const handleRequest = async () => {
// this hits an api to get tweets and sets the tweets
// is this where the LatestTweetsComponent render is called?
}
return (
<div>
<button onClick={() => handleRequest()}>View Latest Tweets</button>
<LatestTweetsComponent tweets={tweets} />
</div>
)
}
const LatestTweetsComponent = props => {
return (
<div>
{props.map((tweet, index) => {
return <p key={index}>{tweet}</p>
})}
</div>
)
}
export default LatestTweets
i think this is because you are trying to map over "props", while you should be mapping over "props.tweets"
try this :
const LatestTweetsComponent = props => {
return (
<div>
{props.tweets.map((tweet, index) => {
return <p key={index}>{tweet}</p>
})}
</div>
)
}
Use that handleFetch in useEffect. So, whenever your state gets change useeffect will rerender that change.
And use tweets as argument in useEffect
I'm trying to use the function below (renderMatchedLogs) to render values from the object it receives, and I'm able to console.log the values but nothing displays on the screen.
I thought JSX can be rendered on the screen from another function? But I'm not sure if this something I misinterpreted or if my logic is off.
Further details of the code:
In the render() {} portion of the code:
<button onClick={this.findMatches}>Find Matches</button>
Which triggers this function to find matches:
findMatches = () => {
const foodLog = this.state.foodLog;
const foodFilter = this.state.foodFilter;
console.log("food filter", foodFilter);
Object.keys(foodLog).map((key, index) => {
if (foodLog[key].foodSelectedKey.some((r) => foodFilter.includes(r))) {
const matchedLog = foodLog[key];
this.renderMatchedLogs(matchedLog);
} else {
// do nothing
}
});
};
And then this is the function to render the values:
renderMatchedLogs = (matchedLog) => {
return (
<div>
{matchedLog.dateKey}
<br />
{matchedLog.mealKey}
<br />
{matchedLog.foodSelectedKey}
<br />
{matchedLog.reactionKey}
<br />
</div>
);
};
You’re rendering it, but not telling the application where to put it. I’d recommend putting the matchedLogs items in state somewhere that you update when you call findMatches, and then within your actual component have a something that looks like this
<div>
{matchedLogs && (renderMatchedLogs())}
<div>
Which can be the same as you have, apart from it’ll read the actual data from the state and render it rather than doing all of that itself (as I’m seeing from this context you want that to be user triggered).
May be what you're looking for is something like this?
state = {
logs: null
}
renderMatchedLogs = (matchedLog) => {
return (
<div>
{matchedLog.dateKey}
<br />
{matchedLog.mealKey}
<br />
{matchedLog.foodSelectedKey}
<br />
{matchedLog.reactionKey}
<br />
</div>
);
};
findMatches = () => {
const foodLog = this.state.foodLog;
const foodFilter = this.state.foodFilter;
console.log("food filter", foodFilter);
const matchedLogs = [];
//use forEach instead to push to matchedLogs variable
Object.keys(foodLog).forEach((key, index) => {
if (foodLog[key].foodSelectedKey.some((r) => foodFilter.includes(r))) {
const matchedLog = foodLog[key];
// this will return the div element from renderMatchedLogs
matchedLogs.push(this.renderMatchedLogs(matchedLog));
}
});
const logs = (<>
{matchedLogs.map(div => div)}
</>);
this.setState({
logs
})
};
render(){
return (
<>
{this.state.logs}
<button onClick={this.findMatches}>Find Matches</button>
</>
)
}
If I have a component like
const Comp = ({children}) => {
//some code
return (
<div>
{children}
</div>
)
}
and then call it like
<Comp>
<input onChange={...} />
<input onChange={...} />
</Comp>
How can I change the focus to the first input field of this component when the component renders, from within the Comp component.
Ideally I would like to have a useEffect function or something which looks something like
useEffect(() => {
thisComponent.firstChild.focus()
})
You need two things, useRef and useEffect, useRef for getting target element ref, and useEffect for handling focusing when then component renders.
children in a component props is an array, so you can manipulate it, you can use index to get which child element you want to set ref, and then call focus() by ref in useEffect:
function App(props) {
const firstChildRef = useRef(null);
useEffect(() => {
if(firstChildRef.current) {
firstChildRef.current.focus()
}
}, [firstChildRef])
return (
<div className="App">
{props.children.map((child, index) => {
if(index === 0) {
return {...child, ref: firstChildRef};
}
return child;
})}
</div>
);
}
I'd like to react rerender component after every state edit.
App component:
let [cur1, setCur1] = useState('USD')
let [cur2, setCur2] = useState('EUR')
let [result, setResult] = useState(0)
let currenciesArr = [cur1, cur2]
async function getRate(e) {
e.preventDefault()
setCur1(cur1 = e.target.cur1.value)
setCur2(cur2 = e.target.cur2.value)
let amount = e.target.amount.value
const api_url = await fetch(`https://free.currconv.com/api/v7/convert?q=${cur1}_${cur2}&compact=ultra&apiKey=${API_KEY}`)
const data = await api_url.json()
await setResult(convert(amount, data))
}
I have used Context.Provider for rerender, but it doesn't work.
return (
<Context.Provider value={{currenciesArr}}>
<div>
<Choose getRate={getRate} chooseCur={chooseCur} chooseCur2={chooseCur2}/>
<ShowRate currencies={currenciesArr} result={result}/>
</div>
</Context.Provider>
)
Component that need to rerender
function Choose(props) {
const cProps = useContext(Context)
console.log(cProps.currenciesArr);
return(
<div>
<div>
<button onClick={ props.chooseCur } name='RUB'>RUB</button>
<button onClick={ props.chooseCur } name='AUD'>AUD</button>
</div>
<div>
<button onClick={ props.chooseCur2 } name='EUR'>EUR</button>
<button onClick={ props.chooseCur2 } name='GBP'>GBP</button>
</div>
<form onSubmit={props.getRate}>
{cProps.currenciesArr.map((item,i) => {
return(
<input type='text' key={i} name={'cur'+(i+1)} defaultValue={item}></input>
)
})
}
<input type='text' name='amount' defaultValue='1'></input>
<button onClick={(e)=>{console.log(e.target)}} ></button>
</form>
</div>
)
}
Button with prop props.chooseCur setting state in App component
function chooseCur(e) {
e.preventDefault()
setCur1(e.target.name)
}
function chooseCur2(e) {
e.preventDefault()
setCur2(e.target.name)
}
and i'd like to "choose" component will rerender after setState.
First currenciesArr should be part of the state as const [currenciesArr, setCurrenciesArr] = useState([cur1, cur2])
Next, you need to call setCurrenciesArr in your chooseCur2 functions. I used a restructuring assignment to get the value of name inside the function. Hooks are called when the event loop is complete. See Capbase Medium post for more information on hooks and the event loop.
In choose.js
You need to use value in your input instead of defaultValue and set it as readonly to prevent receiving a warning about setting the value.
Default value provides the value if none is present.
See the following codesandbox for a working version.
https://codesandbox.io/s/long-rain-8vyuh
I am looking to create a stateless component who's input element can be validated by the parent component.
In my example below, I am running into a problem where the input ref is never being assigned to the parent's private _emailAddress property.
When handleSubmit is called, this._emailAddress is undefined. Is there something I'm missing, or is there a better way to do this?
interface FormTestState {
errors: string;
}
class FormTest extends React.Component<void, FormTestState> {
componentWillMount() {
this.setState({ errors: '' });
}
render(): JSX.Element {
return (
<main role='main' className='about_us'>
<form onSubmit={this._handleSubmit.bind(this)}>
<TextInput
label='email'
inputName='txtInput'
ariaLabel='email'
validation={this.state.errors}
ref={r => this._emailAddress = r}
/>
<button type='submit'>submit</button>
</form>
</main>
);
}
private _emailAddress: HTMLInputElement;
private _handleSubmit(event: Event): void {
event.preventDefault();
// this._emailAddress is undefined
if (!Validators.isEmail(this._emailAddress.value)) {
this.setState({ errors: 'Please enter an email address.' });
} else {
this.setState({ errors: 'All Good.' });
}
}
}
const TextInput = ({ label, inputName, ariaLabel, validation, ref }: { label: string; inputName: string; ariaLabel: string; validation?: string; ref: (ref: HTMLInputElement) => void }) => (
<div>
<label htmlFor='txt_register_first_name'>
{ label }
</label>
<input type='text' id={inputName} name={inputName} className='input ' aria-label={ariaLabel} ref={ref} />
<div className='input_validation'>
<span>{validation}</span>
</div>
</div>
);
You can useuseRef hook which is available since v16.7.0-alpha.
EDIT: You're encouraged to use Hooks in production as of 16.8.0 release!
Hooks enable you to maintain state and handle side effects in functional components.
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Read more in Hooks API documentation
EDIT: You now can with React Hooks. See the answer by Ante Gulin.
You can't access React like methods (like componentDidMount, componentWillReceiveProps, etc) on stateless components, including refs. Checkout this discussion on GH for the full convo.
The idea of stateless is that there isn't an instance created for it (state). As such, you can't attach a ref, since there's no state to attach the ref to.
Your best bet would be to pass in a callback for when the component changes and then assign that text to the parent's state.
Or, you can forego the stateless component altogether and use an normal class component.
From the docs...
You may not use the ref attribute on functional components because they don't have instances. You can, however, use the ref attribute inside the render function of a functional component.
function CustomTextInput(props) {
// textInput must be declared here so the ref callback can refer to it
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
<div>
<input
type="text"
ref={(input) => { textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
This is late but I found this solution much better.
Pay attention to how it uses useRef & how properties are available under current property.
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
For more reference check react docs
The value of your TextInput is nothing more than a state of your component. So instead of fetching the current value with a reference (bad idea in general, as far as I know) you could fetch the current state.
In a reduced version (without typing):
class Form extends React.Component {
constructor() {
this.state = { _emailAddress: '' };
this.updateEmailAddress = this.updateEmailAddress.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
updateEmailAddress(e) {
this.setState({ _emailAddress: e.target.value });
}
handleSubmit() {
console.log(this.state._emailAddress);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
value={this.state._emailAddress}
onChange={this.updateEmailAddress}
/>
</form>
);
}
}
You can also get refs into functional components with a little plumbing
import React, { useEffect, useRef } from 'react';
// Main functional, complex component
const Canvas = (props) => {
const canvasRef = useRef(null);
// Canvas State
const [canvasState, setCanvasState] = useState({
stage: null,
layer: null,
context: null,
canvas: null,
image: null
});
useEffect(() => {
canvasRef.current = canvasState;
props.getRef(canvasRef);
}, [canvasState]);
// Initialize canvas
useEffect(() => {
setupCanvas();
}, []);
// ... I'm using this for a Konva canvas with external controls ...
return (<div>...</div>);
}
// Toolbar which can do things to the canvas
const Toolbar = (props) => {
console.log("Toolbar", props.canvasRef)
// ...
}
// Parent which collects the ref from Canvas and passes to Toolbar
const CanvasView = (props) => {
const canvasRef = useRef(null);
return (
<Toolbar canvasRef={canvasRef} />
<Canvas getRef={ ref => canvasRef.current = ref.current } />
}