In my react application, I create this CreateText component, which is used to write a post or comment on a post. This is the component.
import React from 'react';
import PropTypes from 'prop-types';
import { Header, TextAreaFieldGroup, Button, Spinner } from '../UI';
const CreateText = props => {
const { createPostData, textCreating, onInputChange, onCreateText } = props;
const setButtonText = () => {
let content = <Header Tag='span' text={createPostData.buttonText} />;
if (textCreating)
content = <Spinner width={20} height={20} borderWidth={4} borderTopWidth={4} />;
return content;
}
return (
<div className="post-form mb-3">
<div className="card card-info">
<div className="card-header bg-info text-white">
{createPostData.header}
</div>
<div className="card-body">
<form onSubmit={onCreateText}>
<div className="form-group">
<TextAreaFieldGroup
placeholder={createPostData.placeholder}
name={createPostData.name}
value={createPostData.text}
error={createPostData.error}
onChange={onInputChange}
/>
</div>
<Button
type='submit'
className='btn btn-dark col-md-2'
disabled={textCreating}
>
{setButtonText()}
</Button>
</form>
</div>
</div>
</div>
);
}
CreateText.propTypes = {
createPostData: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired,
onCreateText: PropTypes.func.isRequired
}
export default CreateText;
I didn't use a local state for this component. I used onInputChange() prop and implemented on the 2 parent components which I had used this component. My question is, what is the best way to implement this component? Having a state inside the component and use onChange() inside the component? Or pass it as a prop and implement in the parent component? Everywhere I used the approach that I have shown here. Which is the better approach?
If your verification for TextAreaFieldGroup is complex, your solution is good in this case. Because TextAreaFieldGroup receive createPostData from it parents.
If your verification is simple, just keep value on component's state and verify within component.
I just wondering why your form just has only one text field? If your form has more than one field, the best practice is using render-props pattern
Related
When creating an own child function component and use it within <Dropzone>...</Dropzone>, I see the following warning message, inside the console:
react-dom.development.js:67 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of `Dropzone`.
at MyContainer (http://localhost:3000/static/js/bundle.js:83:12)
at http://localhost:3000/static/js/bundle.js:35421:23
at header
at div
at App
My code looks like that:
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Dropzone from 'react-dropzone';
export const MyContainer = ({...other}) => (
<div {...other}/>
);
export function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p data-testid="projectNameInput">
Hello World
</p>
<Dropzone onDrop={acceptedFiles => console.log(acceptedFiles)}>
{({getRootProps, getInputProps}) => (
<MyContainer {...getRootProps()}>
<section>
<div>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
</section>
</MyContainer>
)}
</Dropzone>
</header>
</div>
);
}
export default App;
Everything seems to work as expected with the Dropzone and the message disappears, when I don't use {...getRootProps()} as attribute-list of MyContainer.
Of course I have checked the react documentation about forwardRef (https://reactjs.org/docs/forwarding-refs.html) and searched for an explanation of this issue. However all explanations do not really fit to my case, because I don't use any reference here at all. At least I don't see the usage of a reference.
Is this an issue of react-dropzone or do I use the dropzone wrongly?
I ran into the same warning a while back and found that when you call getRootProps it returns several different props, one of them is ref:
const { ref, ...rest } = getRootProps();
So you are actually using ref even though it is not obvious. If you want to use Dropzone on your custom component (MyContainer in your case), you have to add Ref forwarding on MyContainer:
const MyContainer = React.forwardRef(ref, {...other}) => (
<div ref={ref} {...other}/>
);
OR
You can extract the ref value and pass it as a custom prop:
// MyContainer
export const MyContainer = ({ innerRef, ...other}) => ( // make component accept prop innerRef which will be passed to the div's ref prop
<div ref={innerRef} {...other}/>
);
// App (omitted code before and after)
<Dropzone onDrop={acceptedFiles => console.log(acceptedFiles)}>
{({ getRootProps, getInputProps }) => {
const { ref, ...rootProps } = getRootProps(); // extract ref
return (
<MyContainer innerRef={ref} {...rootProps}> // pass ref via custom prop innerRef
<section>
<div>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
</section>
</MyContainer>
);
}}
</Dropzone>
Hope it helps!
I am new to learning React and doing a little test project each day. Today, I am trying to create an input box that when I click a Submit button, it alerts the square of a number. Nice and simple. But, I am trying to do this without using State. Just trying to understand how. Here is my code but something is missing. I think I am close!
Any ideas?
import { render } from '#testing-library/react';
import React from 'react';
class App extends React.Component {
sayHi = props => {
alert(this.props.mySentProps);
};
squareTheNumber = () => {
alert('this is the squared number'+ );
};
render() {
return (
<div>
<div onClick={this.sayHi}>
<h1>Hello World</h1>
</div>
<div>
<input type="text" placeholder={'Enter a number to square'} />
</div>
<div>
<button onClick={this.squareTheNumber}>Submit me</button>
</div>
</div>
);
}
}
export default App;
Try this:
import React from "react";
import "./styles.css";
class App extends React.Component {
sayHi = (props) => {
alert(this.props.mySentProps);
};
squareTheNumber = (event) => {
event.preventDefault();
// Should be the same as input's "name" or "id" property
// Docs: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements
const { number } = event.target.elements;
alert(`this is the squared number: ${number.value ** 2}`);
};
render() {
return (
<div>
<div onClick={this.sayHi}>
<h1>Hello World</h1>
</div>
<div>
<form onSubmit={this.squareTheNumber}>
<input
name="number"
type="text"
placeholder="Enter a number to square"
/>
<button type="submit">Submit me</button>
</form>
</div>
<div></div>
</div>
);
}
}
export default App;
P.S.: render from #testing-library/react is used for testing purposes only. See docs here. Class components have their own field with the same name.
As said, there is no clean way to do it without state or any extensions. The best way is to use state and make things clean. But another way you can do it is to use JQuery.
For example:
You can assign the <input> an id, say myId. Then you do this:
var content = $('#myId').content;
And then you can change the content in the p by assigning it a new value.
But using JQuery kinds of defeats the purpose of React, so I would recommend using state.
You can use refs to access mounted elements directly.
https://reactjs.org/docs/refs-and-the-dom.html
The real situation is that I want to get the value of radio button in one file and let them show in another file, therefore I can let user know what they select before.
The code that use select the radio button:
class Usurvey extends Component {
constructor(props){
super(props);
this.state = {
uid: uuid.v1(),
studentName: '',
answers: {
answer1: '',
answer2: '',
answer3: ''
},
isSubmitted: false
};
this.nameSubmit = this.nameSubmit.bind(this);
this.answerSelected = this.answerSelected.bind(this);
this.questionSubmit = this.questionSubmit.bind(this);
}
render(){
<form onSubmit={this.questionSubmit}>
<div className="card">
<div className="video">
<ReactPlayer url={Array[0].url} />
</div>
<label>Do you think the video is fake or real? </label> <br />
<input type="radio" name="answer1" value="real" onChange={this.answerSelected} />Real
<input type="radio" name="answer1" value="fake" onChange={this.answerSelected} />Fake
</div>
<div className="card">
<div className="video">
<ReactPlayer url={Array[0].url} />
</div>
<label>Do you think the video is fake or real? </label> <br />
<input type="radio" name="answer2" value="real" onChange={this.answerSelected} />Real
<input type="radio" name="answer2" value="fake" onChange={this.answerSelected} />Fake
</div>
return(
<div>
{studentName}
{questions}
</div>
);
}
}
export default Usurvey;
The code file I want to input the value of radio button.
'use strict'
import React, { useState } from 'react';
import ReactPlayer from 'react-player';
import Array from '../Array';
import Usurvey from '../Usurvey2';
export default () => {
return (
<div>
<div className="video">
<ReactPlayer url={Array[0].url} playing/>
<div>
<p>Your guess: </p>
<p>{Usurvey.props.Answer[0]}</p>
<p>Right Answer:</p>
<p>{Array[0].name}</p>
</div>
</div>
</div>
)
}
Now I tried to import the Usurvey from the last file and use prop to get answer, but it does not work
Props are not static or hardcoded data that can be accessed by just import the file. Moreover, this is not a way to pass props from a parent component to a child component.
Either you need to lift up the state in Usurvey component to its parent component from where you can pass it to the component where you want the radio button's value.
Or you can pass onSubmit from handler as a prop to Usurvey component from its parent component. The parent component will store the form data and pass it to the required component.
Or if both the component are at a different level of hierarchy then you can also use redux instead of passing it to each component in the path.
You can read more about components and props over here.
Edit
class ParentComponent extends Component {
state = {
submittedQuestion: {} // depending on your logic you can store all the
questions or just one.
}
onQuestionSubmit = () => {
// store data in state.
}
render (){
const {submittedQuestion} = this.state;
return (
<Usurvey onQuestionSubmit={this.onQuestionSubmit} />
<QuestionSubmittedView submittedQuestion={submittedQuestion}/>
);
}
}
I'am creating React.js Weather project. Currently working on toggle switch which converts celcius to fahrenheit. The celcius count is created in one component whereas toggle button is created in another component. When the toggle button is clicked it must trigger the count and display it. It works fine when both are created in one component, but, I want to trigger the function from another component. How could I do it? Below is the code for reference
CelToFahr.js (Here the count is displayed)
import React, { Component } from 'react'
import CountUp from 'react-countup';
class CeltoFahr extends Component {
state = {
celOn: true
}
render() {
return (
<React.Fragment>
{/* Code for celcius to farenheit */}
<div className="weather">
<div className="figures">
<div className="figuresWrap2">
<div className="mainFigureWrap">
<CountUp
start={!this.state.celOn ? this.props.temp.cel : this.props.temp.fahr}
end={this.state.celOn ? this.props.temp.cel : this.props.temp.fahr}
duration={2}
>
{({ countUpRef, start}) => (
<h1 ref={countUpRef}></h1>
)}
</CountUp>
</div>
</div>
</div>
</div>
{/*End of Code for celcius to farenheit */}
</React.Fragment>
)
}
}
export default CeltoFahr
CelToFahrBtn (Here the toggle button is created)
import React, { Component } from 'react'
import CelToFahr from './CeltoFahr'
class CelToFahrBtn extends Component {
state = {
celOn: true
}
switchCel = () => {
this.setState({ celOn: !this.state.celOn })
}
render = (props) => {
return (
<div className="button" style={{display: 'inline-block'}}>
<div className="weather">
<div className="figures">
<div className="figuresWrap2">
<div className="mainFigureWrap">
<div onClick={this.switchCel} className="CelSwitchWrap">
<div className={"CelSwitch" + (this.state.celOn ? "" : " transition")}>
<h3>C°</h3>
<h3>F°</h3>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default CelToFahrBtn
Here when I click on switchCel it must trigger the celcius to fahrenheit value and vice-versa. How to do it? Any suggestions highly appreciated. Thanks in advance
I would have the celToFahr be the parent component of the celToFahrBtn and then pass the function you want to invoke via props
<CellToFahrBtn callback={yourfunction}/>
What else could you do is having a common parent for these to components where you would again do the execution via props and callbacks
The 3rd option would be having a global state which would carry the function like Redux or Reacts own Context. There again you would get the desired function via props and you would execute it whenever you like. This is the best option if your components are completely separated in both the UI and in source hierarchically, but I don't think this is the case in this case.
https://reactjs.org/docs/context.html
These are pretty much all the options you have
To achieve this you'd need to lift your state up and then pass the state and handlers to the needed components as props.
CeltoFahr & CelToFahrBtn would then become stateless components and would rely on the props that are passed down from TemperatureController
class TemperatureController extends Component {
state = {
celOn: true
}
switchCel = () => {
this.setState({ celOn: !this.state.celOn })
}
render () {
return (
<React.Fragment>
<CeltoFahr celOn={this.state.celOn} switchCel={this.state.switchCel} />
<CelToFahrBtn celOn={this.state.celOn} switchCel={this.state.switchCel}/>
</React.Fragment>
)
}
}
It's probably better explained on the React Docs https://reactjs.org/docs/lifting-state-up.html
See this more simplified example:
import React, {useState} from 'react';
const Display = ({}) => {
const [count, setCount] = useState(0);
return <div>
<span>{count}</span>
<Button countUp={() => setCount(count +1)}></Button>
</div>
}
const Button = ({countUp}) => {
return <button>Count up</button>
}
It's always possible, to just pass down functions from parent components. See Extracting Components for more information.
It's also pretty well described in the "Thinking in React" guidline. Specifically Part 4 and Part 5.
In React you should always try to keep components as dumb as possible. I always start with a functional component instead of a class component (read here why you should).
So therefore I'd turn the button into a function:
import React from 'react';
import CelToFahr from './CeltoFahr';
function CelToFahrBtn(props) {
return (
<div className="button" style={{ display: 'inline-block' }}>
<div className="weather">
<div className="figures">
<div className="figuresWrap2">
<div className="mainFigureWrap">
<div onClick={() => props.switchCel()} className="CelSwitchWrap">
<div
className={'CelSwitch' + (props.celOn ? '' : ' transition')}
>
<h3>C°</h3>
<h3>F°</h3>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default CelToFahrBtn;
And you should put the logic in the parent component:
import React, { Component } from 'react';
import CountUp from 'react-countup';
import CelToFahrBtn from './CelToFahrBtn';
class CeltoFahr extends Component {
state = {
celOn: true
};
switchCel = () => {
this.setState({ celOn: !this.state.celOn });
};
render() {
return (
<>
<div className="weather">
<div className="figures">
<div className="figuresWrap2">
<div className="mainFigureWrap">
<CelToFahrBtn switchCel={this.switchCel} celOn={celOn} />
</div>
</div>
</div>
</div>
</>
);
}
}
I'm trying to store a button element globally using react context
const QuitLesson = ({goToLessons}) => {
// this is where I'm trying to create a new context
const closeBtnContext = React.createContext();
return (
<div>
<div styleName='close'>
<button
id='closeLessonBtn'
type='button'
styleName='closeIcon'
onClick={() => setQuitOverlayOpen(true)}
disabled={quitOverlayOpen}>
<img src='/resources/img/btn_close.svg'/>
</button>
</div>
{quitOverlayOpen &&
<Overlay
onClose={() => setQuitOverlayOpen(false)}
onConfirm={goToLessons}
/>
}
</div>
);
};
QuitLesson.propTypes = {
goToLessons: PropTypes.func.isRequired
};
export default CSSModules(QuitLesson, styles);
How do I add this button to context so I can use it in another component later?
You can't createContext inside a functional component.
// Declair this outside of the QuitLesson functional component.
const closeBtnContext = React.createContext();
const QuitLesson = ({goToLessons}) => {
return (
<div>
<div styleName='close'>
<button
id='closeLessonBtn'
type='button'
styleName='closeIcon'
onClick={() => setQuitOverlayOpen(true)}
disabled={quitOverlayOpen}>
<img src='/resources/img/btn_close.svg'/>
</button>
</div>
{quitOverlayOpen &&
<Overlay
onClose={() => setQuitOverlayOpen(false)}
onConfirm={goToLessons}
/>
}
</div>
);
};
QuitLesson.propTypes = {
goToLessons: PropTypes.func.isRequired
};
export default CSSModules(QuitLesson, styles);
I also think you maybe trying to use context in a way that it wasn't intended. the context is for passing data (state) and functions primarily to update that state.
from https://reactjs.org/docs/context.html
Context is designed to share data that can be considered “global” for
a tree of React components, such as the current authenticated user,
theme, or preferred language.
It sounds like you want to store the actual component in state to then reuse later. A better way of doing this that is 'React' friendly would be to create the closeBtnContext.context and wrap the app in the closeBtnContext.provider and then wrap the QuitLesson component with the closeBtnContext.consumer and then import the QuitLesson component wherever it is needed in your app.