I have a React container with a number of child components. One of which is supposed to be a modal that will show the user their name which is fetched from a user data api in the parent container. I should be able to pass the user data into the child with a prop, but must be missing something, as the display name does not show in the input as the value.
Parent Container
class ParentContainer extends React.Component {
constructor (props) {
super(props)
this.state = {
displayName: this.state.user.displayName
}
this.config = this.props.config
}
async componentDidMount () {
try {
const userData = await superagent.get(`/api/user`)
await this.setState({ user: userData.body })
console.log(userData.body.displayName) <===logs out user display name
} catch (err) {
console.log(`Cannot GET user.`, err)
}
}
render () {
return (
<div className='reviews-container'>
<ReviewForm
config={this.config} />
<ReviewList
reviews={reviews}
ratingIcon={this.ratingIcon}
/>
<DisplayNameModal
config={this.config}
displayName={this.displayName} />
</div>
)
}
}
export default ParentContainer
Child Component
class DisplayNameModal extends React.Component {
constructor (props){
super(props)
this.state = {
displayName: this.props.displayName
}
}
render (props) {
const {contentStrings} = this.props.config
return (
<div className='display-name-container' style={{ backgroundImage: `url(${this.props.bgImgUrl})` }}>
<h2 className='heading'>{contentStrings.displayNameModal.heading}</h2>
<p>{contentStrings.displayNameModal.subHeading}</p>
<input type="text" placeholder={this.props.displayName}/>
<button
onClick={this.submitName}
className='btn btn--primary btn--md'>
<span>{contentStrings.displayNameModal.button}</span>
</button>
<p>{contentStrings.displayNameModal.cancel}</p>
</div>
)
}
}
export default DisplayNameModal
I found that adding displayName: userData.body.displayName to setState and then wrapping the component in the parent with
{this.state.displayName &&
<div>
<DisplayNameModal
config={this.config}
displayName={this.state.displayName} />
</div>
}
works as the solution.
The prop should be passed by:
<DisplayNameModal
config={this.config}
displayName={this.state.displayName} />
where you are using:
<DisplayNameModal
config={this.config}
displayName={this.displayName} />
You have set the displayName on state in the parent, anything you refer to from state should be referred to as this.state.foo, where as any method on that component can be referred to as this.foo.
First of all, you fetch the data in wrong way, you can check it here:
componentDidMount () {
superagent.get(`/api/user`).then(res => this.setState({ user: res.body }))
}
the second one, initialize default state for displayName, for example, an empty string, it will be replaced when promise retrieves data data from the server:
constructor (props){
super(props)
this.state = {
displayName: ''
}
}
and pass this state as props to your child component:
render () {
return (
<div className='reviews-container'>
<ReviewForm
config={this.config} />
<ReviewList
reviews={reviews}
ratingIcon={this.ratingIcon}
/>
<DisplayNameModal
config={this.config}
displayName={this.props.displayName} />
</div>
)
}
in your child component, you can simply call this props:
<input type="text" placeholder={this.props.displayName}/>
Related
I want to setstate of PageSearchByExcel's class but I know that (this) is no longer to PageSearchByExcel.
Have you some way to set state to a totalMatchSample variable.
I bring this code from ant-design Official web.
https://ant.design/components/upload/
I'm very new to react.
Help me, please.
Or If you have another way that is better than this way please give it to me.
import ...
const props = {
name: 'file',
multiple: true,
action: API_URL + '/api/search/excelfile',
onChange(info) {
const status = info.file.status;
const data = new FormData()
data.append('file', info.file.originFileObj, info.file.name)
Axios.post(props.action, data)
.then((Response) => {
this.setState({
totalMatchSample: info.file.response.length
})
})
},
};
export default class PageSearchByExcel extends React.Component {
constructor(props) {
super(props)
this.state = {
totalSample: 0,
totalMatchSample: 0
}
}
render() {
return (
<div>
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
</Dragger>
<div>
<p>
{this.state.totalMatchSample} matched samples from
{this.state.totalSample} samples
</p>
<br />
</div>
</div>
)
}
}
Since you're declaring props outside the PageSearchByExcel component the this refers to props object itself and not the component. You can define the onChange method on the component and pass it down as a prop to Dragger which then will be correctly bound to PageSearchByExcel.
import ...
const props = {
name: 'file',
multiple: true,
action: API_URL + '/api/search/excelfile',
};
export default class PageSearchByExcel extends React.Component {
constructor(props) {
super(props)
this.state = {
totalSample: 0,
totalMatchSample: 0
}
}
// Define onChange here
onChange = (info) => {
const status = info.file.status;
const data = new FormData()
data.append('file', info.file.originFileObj, info.file.name)
Axios.post(props.action, data)
.then((Response) => {
this.setState({
totalMatchSample: info.file.response.length
})
})
}
render() {
return (
<div>
<Dragger {...props} onChange={this.onChange}>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
</Dragger>
<div>
<p>
{this.state.totalMatchSample} matched samples from
{this.state.totalSample} samples
</p>
<br />
</div>
</div>
)
}
}
Hope this helps !
#sun, based on what you posted, i will assume that you have some sort of props being passed to PageSearchByExcel component.
having said that, that props object, its an anti-pattern, you really want to pass each key in that props object to PageSearchByExcel down via the props system.
ex:
Class ParentComponent ...
...some code
render () {
..someJSX
<PageSearchByExcel name='file' multiple={true} />
}
this will basically setup your props inside PageSearchByExcel
now thats out of the way, let's talk about setState({}) and loading resources
in your PageSearchByExcel Component, you would have something like this
export default class PageSearchByExcel extends React.Component {
constructor(props) {
super(props)
this.state = {
totalSample: 0,
totalMatchSample: 0
}
}
// define your axios call inside your component class NOT OUTSIDE
loadResource = () => {
// ....yourAxiosCodeHere
}
// You then set the state at the first load of the component
componentDidMount () {
this.loadResource()
}
render() {
return (
<div>
<Dragger {...this.props}>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
</Dragger>
<div>
<p>
{this.state.totalMatchSample} matched samples from
{this.state.totalSample} samples
</p>
<br />
</div>
</div>
)
}
}
TAKEAWAY::
1.) define your class methods inside the class itself in order the properly reference 'this'
2.) make sure you pass the props down from the parent component down to your PageSearchByExcel
3.) make sure you load your resource using the react life cycle method
This should get you going.
i found a gist about how to pass state between two components.
Here the jsbin
But how about the multi state?
I want two input fields and show the entered text in other components when i edit it.
i tried edited like this
this.state = {
fieldVal: "" //first input state
otherFieldVal: "" //second
}
and
//input onChange
onUpdate = name => (event) => {
this.setState({ [name]: event.target.value });
};
with no luck.
How can i made it work on multi state for multi input fields ?
Don't need to keep state in both Child and parent. You can write your child component like below, and you can access tow states dynamically by using data-attirb or you can folloe #Isaac 's answer.Keep the state in Child and pass state to Parent or keep the event to Parent from Child.
export class Child extends React.Component {
update = (e) => {
this.props.onUpdate(e.target)
};
render() {
return (
<div>
<h4>Child</h4>
<input
type="text"
placeholder="type here"
onChange={this.update}
data-state = "fieldVal"
value={this.props.fieldVal}
/><br/><br/>
<input
type="text"
placeholder="type here"
onChange={this.update}
data-state = "otherFieldVal"
value={this.props.otherFieldVal}
/>
</div>
)
}
}
export class OtherChild extends React.Component {
render() {
return (
<div>
<h4>OtherChild</h4>
Value in OtherChild Props passedVal1: {this.props.passedVal1} <br/>
Value in OtherChild Props passedVal2: {this.props.passedVal2}
</div>
)
}
}
and in parent :
class App extends Component {
onUpdate = (data) => {
this.setState({
[data.dataset.state]: data.value
})
};
render() {
return (
<div>
<h2>Parent</h2>
Value in Parent Component State fieldVal: {this.state.fieldVal} <br/>
Value in Parent Component State otherFieldVal: {this.state.otherFieldVal}
<br/>
<Child onUpdate={this.onUpdate} fieldVal= {this.state.fieldVal} otherFieldVal ={this.state.otherFieldVal}/>
<br />
<OtherChild passedVal1={this.state.fieldVal} passedVal2={this.state.otherFieldVal}/>
</div>
);
}
}
demo
renderInput = (prop) => {
return (
<Input
onChange={(event) => {
this.setState({ [prop]: event.target.value });
}}
/>
)
}
render() {
<div>
{this.renderInput('name')}
{this.renderInput('age')}
</div>
}
We can set a renderInput method and render different input using parameter to achieve your objective
I have a parent container which has a couple of child components. When the user clicks onClick={props.toggleReviewForm}, the function
toggleReviewForm () {
this.setState(prevState => ({
reviewFormActive: !prevState.reviewFormActive,
displayNameModalActive: !prevState.displayNameModalActive
}))
}
toggles the reviewForm state to visible. It's visibility is set with reviewFormActive={reviewFormActive} in the child component and the parent has `this.state = {reviewFormActive: false} set in the constructor. I am passing
displayNameModalActive={displayNameModalActive}
into the child component for the modal, but getting the error
Uncaught TypeError: Cannot read property 'displayNameModalActive' of undefined at DisplayNameModal.render
Parent Container
class ReviewsContainer extends React.Component {
constructor (props) {
super(props)
this.state = {
reviewFormActive: false,
displayNameModalActive: false
}
this.config = this.props.config
this.toggleReviewForm = this.toggleReviewForm.bind(this)
}
toggleReviewForm () {
this.setState(prevState => ({
reviewFormActive: !prevState.reviewFormActive,
displayNameModalActive: !prevState.displayNameModalActive
}))
}
render () {
const {
reviewFormActive,
displayNameModalActive
} = this.state
return (
<div className='reviews-container'>
<ReviewForm
config={this.config}
reviewFormActive={reviewFormActive}
toggleReviewForm={this.toggleReviewForm}
/>
{this.state.displayName &&
<div className='modal-container'>
<DisplayNameModal
bgImgUrl={this.props.imageUrl('displaynamebg.png', 'w_1800')}
config={this.config}
displayNameModalActive={displayNameModalActive}
displayName={this.state.displayName}
email={this.state.email} />
</div>
}
</div>
)
}
}
export default ReviewsContainer
Child Component (modal)
class DisplayNameModal extends React.Component {
constructor (props){
super(props)
this.state = {
displayName: this.props.displayName,
email: this.props.email.split('#')[0]
}
}
render (props) {
const {contentStrings} = this.props.config
return (
<div>
//Should only allow the modal to show if the username is the same as the email or there is no username available
{ props.displayNameModalActive && this.state.displayName === this.state.email || !this.state.displayName &&
<div className='display-name-container' style={{ backgroundImage: `url(${this.props.bgImgUrl})` }}>
<div className='display-name-content'>
<h2 className='heading'>{contentStrings.displayNameModal.heading}</h2>
<p>{contentStrings.displayNameModal.subHeading}</p>
<input type="text"
defaultValue={this.state.displayName}
placeholder={this.state.displayName}
minLength="3"
maxLength="15"/>
<button
onClick={this.updateDisplayName}
className='btn btn--primary btn--md'>
<span>{contentStrings.displayNameModal.button}</span>
</button>
<p className='cancel'>{contentStrings.displayNameModal.cancel}</p>
</div>
</div>
}
</div>
)
}
}
export default DisplayNameModal
Whyt this:
props.displayNameModalActive
and not this:
this.props.displayNameModalActive
?
Correct me if I'm wrong but render doesn't get props as argument.
I have an app that uploads files via a standard <input type="file"/>. I'm trying to pass the file size of the chosen file(s) to the child to see if it's above a certain size, and if so, display an error. I know you have to pass state values down as props, but I'm unsure as to where/how to call the function to get an updated value. Any help is appreciated.
Edit: I am using the react jsonschema form to build the form: https://github.com/mozilla-services/react-jsonschema-form. Declaring the schemas before the Parent class.
Parent
const schema = {
type: 'object',
required: ['file'],
properties: {
file: { type: 'string', format: 'data-url', title: 'File' }
}
}
const FileWidget = (props) => {
return (
<input type="file" id="fileName" required={props.required} onChange={(event) => props.onChange(event.target.value)} />
)
}
const uiSchema = {
file: {
'ui:widget': FileWidget,
classNames: "uiSchema"
}
}
class Parent extends Component {
constructor(props) {
super(props);
this.state = { fileSize: 0 };
this.getFileSize = this.getFileSize.bind(this);
getFileSize(){
this.setState({fileSize: document.getElementById("fileName").files[0].size});
console.log("FILESIZE:: ", this.state.fileSize);
} //where to call to update the file size?
render() {
return (
<div className="container">
<FileUpload schema={schema} uiSchema={uiSchema} fileSize={this.state.fileSize} />
</div>
)
}
}
export default Parent;
Child
class Child extends Component {
constructor(props) {
super(props);
this.state = { formData: {} };
this.handleSubmit = this.handleSubmit.bind(this);
}
render() {
return (
<div className="container">
<Form
schema={this.props.schema}
uiSchema={this.props.uiSchema}
formData={this.state.formData}
onChange={({ formData }) => this.setState({ formData })}
onSubmit={this.handleSubmit}
>
<div>
<button type="submit" className="btn btn-info">Convert</button>
</div>
</Form>
<div hidden={this.props.fileSize > 100 ? false : true }><h4>File size exceeded.</h4></div>
</div>
)
}
}
export default Child;
class Parent extends Component {
constructor(props) {
super(props);
this.state = { fileSize: 0 };
this.getFileSize = this.getFileSize.bind(this);
getFileSize(){
this.setState({fileSize: document.getElementById("fileName").files[0].size});
console.log("FILESIZE:: ", this.state.fileSize);
} //where to call to update the file size?
componentDidMount(){
// you can call the getFilesize here
this.getFileSize();
}
render() {
return (
<div className="container">
<FileUpload fileSize={this.state.fileSize} />
</div>
)
}
}
export default Parent;
child Component
class FileUpload extends Component {
constructor(props) {
super(props);
this.state = { formData: {} };
this.handleSubmit = this.handleSubmit.bind(this);
}
render() {
return (
<div className="container">
<Form
formData={this.state.formData}
onChange={({ formData }) => this.setState({ formData })}
onSubmit={this.handleSubmit}
>
<div>
<button type="submit" className="btn btn-info">Convert</button>
</div>
</Form>
<div style={{display:this.props.fileSize > 100 ? "block": "none" }><h4>File size exceeded.</h4></div>
</div>
)
}
}
export default FileUpload;
I think you want to show the error message when file size exceeds given size
you can also use componentWillMount to call getfilesize
it actually depends document.getElementById("fileName")
it that particular element has populated the data by the time your parent componentWill Mount you can use componentWillMount lifecycle hook
Parent Component
class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
onFileSelected(e) {
var fileSize = e.target.files.length > 0 ? e.target.files[0].size : false;
if (fileSize)
this.setState({ fileSize });
}
render() {
return (
<div className="App">
<input type="file" onChange={this.onFileSelected.bind(this)} />
<FileUpload fileSize={this.state.fileSize}></FileUpload>
</div>
);
}
}
child Component
class FileUpload extends Component {
render() {
return (
<div>
{this.props.fileSize > 100 ? <h2 >File size exceeds 100</h2> : null}
</div>
);
}
}
in the above code what i did is created <input type="file"/> in parent component and attached a onchange event to it.
Got it to work. Passed the function to the child:
<FileUpload fileSize={this.getFileSize.bind(this)} />
Then in the child added a setState to the form's onChange to call the function:
onChange={({ formData }) => { this.setState({ formData }); this.setState({fileSize:this.props.fileSize()})}}
and displayed the error message accordingly:
<div style={{display: this.state.fileSize > 100 ? "block": "none" }><h4>File size exceeded.</h4></div>
I set a material-ui/TextField in my user-defined component. The user-defined component is named LabelTextField. I render several LabelTextField in my user-defined component which is named TextList. My question is how to get the values of textField in the TextList component.
A button is next to the TextList component in the View component. I will save all the TextField values when someone clicks the button.
I will post a network request in the TextList component to save the value to the backend.
I am using Redux. Does every material-ui/TextField should dispatch the value in the onChange callback function?
The onChange is at the bottom of this website:
http://www.material-ui.com/#/components/text-field
My central code:
LabelTextField:
textChangeFun = (value) => {
}
render() {
return (
<div>
<div style={{fontSize:0}}>
<div style={inlineStyle}>
<FlatButton disableTouchRipple={true} disabled={true} label={this.props.labelValue} />
</div>
<div style={inlineStyle}>
<TextField
hintText={this.props.textValue}
/>
</div>
</div>
</div>
);
}
TextList:
render(){
return (
<div>
{demoData.map((item,id) =>
<LabelTextField key={id} labelValue={item.label} textValue={item.text} ></LabelTextField>
)}
</div>
)
}
You need to give LabelTextField a handler for the change event.
class LabelTextField extends React.Component {
onChange(e) {
this.props.onChange({ id: this.props.id, value: e.currentTarget.value })
}
render() {
return (
<div>
<div style={{fontSize:0}}>
<div style={inlineStyle}>
<FlatButton disableTouchRipple={true} disabled={true} label={this.props.labelValue} />
</div>
<div style={inlineStyle}>
<TextField
hintText={this.props.textValue}
onChange={this.onChange.bind(this)}
/>
</div>
</div>
</div>
);
}
}
class TextList extends React.Component {
constructor() {
super();
this.state.textFields = {}; // TODO: get initial state from demoData
this.onTextFieldChange = this.onTextFieldChange.bind(this);
}
onTextFieldChange = ({ id, value }) {
const { textFields } = this.state;
textFields[id] = value;
this.setState({ textFields });
}
render(){
return (
<div>
{demoData.map((item,id) =>
<LabelTextField key={id} labelValue={item.label} textValue={item.text} onChange={this.onTextFieldChange} ></LabelTextField>
)}
</div>
)
}
}
This way any time a textField changes, it causes the onTextFieldChange handler to be called and the state of TextList to update.
If you have a more complicated situation, you might consider using redux or even http://redux-form.com/6.5.0/