state props overridden by plugin props - reactjs

here is the component contains "react-dropzone" plugin below
class Submit extends Component {
constructor(props) {
super(props)
this.props.appState.recipes = JSON.parse(localStorage.getItem("recipes")) || []
}
submitForm() {
debugger //I also get props properly here.
this.props.appState.recipe.name = this.name.value
this.props.history.push('/home')
}
onImageDrop(files) {
debugger //props overridden by Dropzone props :( appState is undefined
this.props.appState.uploadedFileCloudinaryUrl = files[0]
}
render() {
return (
<form onSubmit={() => this.submitForm()}>
<Dropzone
multiple={false}
accept="image/*"
onDrop={this.onImageDrop}>
<p>Drop an image or click to select a file to upload.</p>
</Dropzone>...
)
}
}
export default Submit
I am able to access mobx props in constructor and form on submit method of form(submitForm()) but if i upload a file to Dropzone and check the props content in "onImageDrop()" function I dont recognaize any of properties. Ok for experienced react developers that makes sense but I couldnt understand why its override my own state props and how I can fix it?

Binding issue. Either prebind onImageDrop in constructor (which is preferred way)
constructor(props) {
super(props)
this.submitForm = this.submitForm.bind(this)
this.onImageDrop = this.onImageDrop.bind(this)
this.props.appState.recipes = JSON.parse(localStorage.getItem("recipes")) || []
}
or use arrow function as you did for submitForm
render() {
return (
<form onSubmit={() => this.submitForm()}>
<Dropzone
multiple={false}
accept="image/*"
onDrop={files => this.onImageDrop(files)}>
<p>Drop an image or click to select a file to upload.</p>
</Dropzone>...
)
}

Related

How to be able to open the file explorer in a class component and read the selected file in React JS

I am trying to open the file explorer based on a props condition.
However, due to a class Component I am not able use the useEffect hook.
Below is my code :
The class component :
class UploadFile extends React.Component {
constructor(props) {
super(props);
this.state = {
isClicked:0
};
this.inputFileRef = React.createRef();
this.onFileExplorerClick = this.onFileExplorerClick.bind(this);
}
onFileExplorerClick(fileRef) {
this.setState ({isClicked:1});
fileRef.current.click();
}
clickMe() {
console.log("this is click Me!");
}
render() {
const loadFile = this.props.loadFile;
return (
<div>
<Button onClick= {()=>this.clickMe()}>Click this </Button>
{ (loadFile &&
<form style={{ visibility: "collapse" }}>
<input type="file" ref={this.inputFileRef} />
<button onClick={this.onFileExplorerClick(this.inputFileRef)}></button>
</form>
) || ''}
</div>
);
}
}
If I am using a function component with useEffect the file explorer is popping up and working, but in class component , I am not able to bring it up and access the file uploaded.
The inputFileRef inside the onFileExplorer click is coming as undefined(or null), hence the click event (this.inpuFileRef.current.click()) is not happening. How can it be captured without actually clicking on a visible Choose File button ?
Please help.
Thanks.
I changed the render function and added life Cycle methods which could solve it.
class UploadFile extends React.Component {
constructor(props) {
super(props);
this.state = {
isClicked:0
};
this.inputFileRef = React.createRef();
this.onFileExplorerClick = this.onFileExplorerClick.bind(this);
}
componentDidMount() {
let loadFile = this.props.loadFile;
if(loadFile) this.setState ({isClicked:1});
}
componentDidUpdate() {
if(this.state.isClicked) this.onFileExplorerClick();
}
onFileExplorerClick() {
this.inputFileRef.current.click();
this.setState ({isClicked:0});
}
clickMe() {
console.log("this is click Me!");
}
render() {
const loadFile = this.props.loadFile;
return (
<div>
<Button onClick= {()=>this.clickMe()}>Click this </Button>
<input type="file" ref={this.inputFileRef} style:{{visibility:"collapse"}} />
</div>
);
}
}

React / TypeError: files.map is not a function

I am trying to upload some files via <input type="file"> to the state, in order to pass it back to the main component. With the code below I get the Uncaught TypeError: Cannot read property 'setState' of undefined when trying to update the state with the uploaded files.
import React, { Component } from 'react'
import * as RB from 'react-bootstrap'
import Button from 'components/Button/Button'
class uploadMob extends Component {
constructor(props) {
super(props)
this.state = {
files: [],
}
}
onFilesAdded(files) {
this.setState((prevState) => ({
files: prevState.files.concat(files),
}))
this.handleUpload()
}
handleUpload = (e) => {
const { pdfUploadToState } = this.props
debugger
pdfUploadToState(this.state.files)
console.log('PUSHED FILE', this.state.files)
}
render() {
const files = this.state.files
return (
<RB.Form.Group>
<div className="upload-btn-wrapper">
<div className="Files">
{files.map((file, key) => {
return (
<div key={key} className="Row">
<span className="Filename">
{file.value}
</span>
</div>
)
})}
</div>
<Button size="sm" variant="light">
Dateien hochladen
</Button>
<input
type="file"
name="file"
id="files"
onChange={this.onFilesAdded}
/>
</div>
</RB.Form.Group>
)
}
}
export default uploadMob
I would very much appreciate the help with this. It is driving me a bit crazy..
The problem is with this line:
onFilesAdded(files) {
You need to either bind() it to this like this:
constructor(props) {
super(props)
this.state = {
files: [],
};
this.onFilesAdded = this.onFilesAdded.bind(this);
}
or convert it to an arrow function:
onFilesAdded = files => {
The problem is this referred inside onFilesAdded does not point the component instance by default. By using the two methods above, we make sure that by calling this., the component is correctly referred.
You need to use an arrow function to have the correct value of this.
setState is asynchronous so you need to pass the this.handleUpload function as a callback in order to updload the files once setState is completed.
From the documentation :
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
onFilesAdded = files => {
this.setState(prevState => ({
files: [...prevState.files, ...files]
}), this.handleUpdload)
}

How to use props of a component out of the component in react with typescript?

As i am new to react i have a question.I have a react component and its properties. And I want to reach one of these properties from the page where i used my component.
type BranchProps = {
SelectedBranch : string
}
class Branch extends React.Component<BranchProps, BranchState> {
constructor(props: BranchProps) {
super(props);
}
render() {
return (
<SelectBox></SelectBox>
)
}
}
export default Branch ;
ParentPage.tsx
import Branch...
class Page extends.... {
ctor..
const test:string = () => {
Branch.SelectedBranch ???
}
}
And i want to get "SelectedBranch" from my ParentPage.
Note: SelectedBranch is changing on change event. Should i make my SelectedBranch a const and export it or what should i do ?
I have created this Input.js child component with different props
const Input = ({ placeholder, label, value, onChangeText, secureTextEntry }) => {
return (
<View >
<Text >{ label }</Text>
<TextInput
secureTextEntry={secureTextEntry}
placeholder={placeholder}
autoCorrect={false}
value={value}
onChangeText={onChangeText}
style={inputStyles}
/>
</View>
);
};
Once I import it to be used on a page, this is how the manipulation of the content is being done. the value is been passed on by simply quoting the specific prop
<Input
secureTextEntry
placeholder={'password'}
label={'Password'}
value={this.state.password}
onChangeText={password => this.setState({ password })}
/>
Here the 'password' is been assigned to the component by using the state of the parent. something like this, you can assign the value as you see fit.
state = { email: '', password: '', error: '', loading: false };
A far better way exist by using the Redux approach. would be advisable to have a look.
Firstly, you should understand the difference between state and props inside a component. Props shouldn't be updated, it's the state's role.
You can't directly access component's props outside of it.
In pure react (without librabry like redux) the right way should be to use callbacks to return the element to the parent.
class Branch extends React.Component<BranchProps, BranchState> {
state = {
'selectedBranch': ''
}
constructor(props: BranchProps) {
super(props);
}
handleOnChange = (e) => {
this.setState({'selectedBranch': e.target.value})
this.props.parentHandleChangeBranch(this.state.selectedBranch);
}
render() {
return (
<SelectBox value={this.state.selectedBranch} onChange="{this.handleOnChange}"></SelectBox>
)
}
}
class Page extends React.Component {
state = {
'branch': null
}
parentHandleChangeBranch = (branch) => {
this.setState({'branch': branch};
}
render () {
<div>
<Branch parentHandleChangeBranch={this.parentHandleChangeBranch} />
</div>
}
}
You can declare a function in the parent component and pass it as prop to the child. Then, call this callback whenever you want inside the child.

React: Get state of children component component in parent

I have this container where and is not placed in the same level. How can I get the state of the Form when I click on the button (which is placed on the parent) ?
I've created a demo to address my issue.
https://codesandbox.io/s/kmqw47p8x7
class App extends React.Component {
constructor(props) {
super(props);
}
save = () => {
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form />
<button onClick={this.save}>save</button>
</div>
);
}
}
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
To access a child instance from parent, your need to know about ref:
First, add formRef at top your App class:
formRef = React.createRef();
Then in App render, pass ref prop to your Form tag:
<Form ref={this.formRef} />
Finaly, get state from child form:
save = () => {
alert("how to get state of Form?");
const form = this.formRef.current;
console.log(form.state)
};
Checkout demo here
ideally, your form submit action belongs to the Form component
You can put button inside your From component and pass a submit callback to the form.
class App extends React.Component {
constructor(props) {
super(props);
}
save = (data) => {
// data is passed by Form component
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form onFormSubmit={this.save} />
</div>
);
}
}
you can write the code like this
https://codesandbox.io/s/23o469kyx0
As it was mentioned, a ref can be used to get stateful component instance and access the state, but this breaks encapsulation:
<Form ref={this.formRef}/>
A more preferable way is to refactor Form to handle this case, i.e. accept onChange callback prop that would be triggered on form state changes:
<Form onChange={this.onFormChange}/>
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
Forms will need to handle this any way; it would be impossible to reach nested form with a ref from a grandparent. This could be the case for lifting the state up.
E.g. in parent component:
state = {
formState: {}
};
onFormChange = (formState) => {
this.setState(state => ({
formState: { ...state.formState, ...formState }
}));
}
render() {
return (
<Form state={this.state.formState} onChange={this.onFormChange} />
);
}
In form component:
handleChange = e =>
this.props.onChange({
[e.target.name]: e.target.value
});
render() {
return (
<input
onChange={this.handleChange}
name="firstName"
value={this.props.state.firstName}
/>
);
}
Here is a demo.

react-dropzone onDrop firing twice

So I'm trying to add a pretty simple file upload to my React + Redux App and I found that Dropzone to be the most convinient way to do it. Here's my setup.
I have a FileInput component
import React from 'react'
import Dropzone from 'react-dropzone'
class FileInput extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
onChange(files) {
// For Redux form
if (this.props.input) {
const {input: {onChange}} = this.props;
onChange(files[0])
}
else if(this.props.onChange){
this.props.onChange(files[0])
}
else{
console.warn('redux-form-dropzone => Forgot to pass onChange props ?');
}
}
render() {
return (
<Dropzone onDrop={ this.onChange } {...this.props} >
Drag&Drop the image <br/> or click to select one
</Dropzone>
)
}
}
export default FileInput
And I use it on tha page like this:
<FileInput
onChange={(file) => console.log('dropped', file)}
className='add-avatar-dropzone'
activeClassName='dropzone-active'
/>
(console.log used for debugging purposes ofcource)
But when I try to drop a file, I get 2 log outputs. The first being the file I dropped, the second - some kind of a Proxy, probably provided by react itself...
I wonder how to get rid of that proxy and why is it doing that in the first place?
Thing I tried
Couple obvious problem-points I tried to eliminate, but did not seem to make any change.
Renaming the onChange function to something else like handleDrop and rewriting it as handleDrop = (files) => {
Removing the constructor (seems weird to assign something to itself)
It ended up being a simple matter and it's really silly for me not to think of it.
The onChange prop in the code above got passed on to Dropzone and then to the input field, wich was the source of the confusion.
I modified the code to work like this:
render() {
const { onChange, ...rest } = this.props
return (
<Dropzone onDrop={ this.onChange } {...rest} >
Drag&Drop the image <br/> or click to select one
</Dropzone>
)
}
That works just fine

Resources