react-dropzone onDrop firing twice - reactjs

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

Related

Refreshing module data on button click in react

I am new to react and using "inspirational-quotes" from https://www.npmjs.com/package/inspirational-quotes trying to load a new quote on button click using bind().
I do not know what i do wrong.
This is the code if have right now (App.js):
enter code here
import React, { useState } from "react";
import './App.css';
const a = 'New Quote';
const Quote = require('inspirational-quotes');
const quotes = Quote.getQuote();
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isAppon: true,
quoteText: quotes.text,
quoteAuthor: quotes.author
};
this.newQuote = this.newQuote.bind(this);
}
newQuote() {
// alert('hee')
// this.setState({ quoteText: this.state.quoteText, quoteAuthor: this.state.quoteAuthor });
this.setState(prevState => ({ isAppon: !prevState.isAppon }));
}
render() {
return (<div className="App" >
<header >
{/* <p> A new quote: {this.state.quoteText} from {this.state.quoteAuthor}</p> */}
<button onClick={this.newQuote}> {a} < /button>
<div> A new quote: {this.state.quoteText} from {this.state.quoteAuthor}</div>
< /header> < /div>
);
}
}
export default App;
So you have a couple of things wrong here.
You're trying to use React Hooks (useEffect for example) in a class component, so that wont work. You'd need to use lifecycle events like componentDidMount.
I'm also not sure if you paste your code correctly as it isn't valid when I paste it, there are just a couple things missing but I can see what you wanted to paste so it's okay.
That said, you're also making your life difficult using a class based component when functional components are a thing in combination with hooks :)
(by the way i'm not saying class components don't have their place, they do. But they are not as beginner friendly)
Here's what you need to do:
import { useState, useEffect } from 'react';
import * as Quote from 'inspirational-quotes';
function App() {
const [quote, setQuote] = useState('');
const getNewQuote = () => {
setQuote(Quote.getQuote())
}
// on mount get a new quote
useEffect(() => {
getNewQuote();
}, [ ])
return (
<>
<button onClick={getNewQuote}>new quote</button>
<p>{ quote.text } - { quote.author } </p>
</>
);
}
export default App;
That said, your the library your using kinda sucks as the getQuote uses a "random" index that is calculated outside of the getQuote method. Because of this clicking the button won't create a new quote. If you can find a way to create a new instance of quote then you'll be in business. I tried a couple ways to achieve this but no luck.
I suggest looking into using a random quote API and modifying the getNewQuote method to use an async fetch request to get your next quote. I'll help you with this in the comments or an edit if you need.
Edit: Update as this question is based on a challenge that asks for a page refresh. See below for updated example:
import { useState, useEffect } from 'react';
import Quote from 'inspirational-quotes';
const App = () => {
const [quote, setQuote] = useState(null);
const getNewQuote = () => {
setQuote(Quote.getQuote());
}
// reload the current page
const refreshPage = () => {
window.location.reload();
}
// on mount get a new quote
useEffect(() => {
getNewQuote();
}, [ ])
return (
<>
<button onClick={refreshPage}>new quote</button>
<p>{ quote?.text } - { quote?.author } </p>
</>
);
}
export default App;
In This example we are keeping track of the quote in state using useState and assigning its value to quote.
We then use useEffect with an empty dependency array to tell React we want to do something when the page loads
We create two handle functions:
getNewQuote is for setting a quote to state (as an object).
refreshPage is a function to reload the current page.

onClick for Cruise List Heading using React

At the moment I am trying to do a website on cruise ships using React in my spare time.
I have a working version on my Reviews branch, here https://github.com/RobertWSON/Personal-ship-project/tree/reviews.
However I am wanting to change how the Cruise Lines Page is displayed.
I would like to have Cruise Line Headings across the page.
When a Cruise Line Heading is clicked it expands to show a List of Ships for that Cruise Line and if you click again, it collapses to show just the Cruise Line Heading.
At the moment I am a bit confused, as to how I can make this work and I have not got it working just yet.
I have been working on this, on a different branch called robs-shipslist-under-cruiselines: here https://github.com/RobertWSON/Personal-ship-project/tree/robs-shipslist-under-cruiselines .
I have components called CruiseListHeader.jsx and ListofShips.jsx.
Just wondering if anyone can give me any advice on whether it's possible to do a ternary operator for this handleClick, that I have in my CruiseListHeader component?
It seems to me that the code inside my handleClick function is the code that causes the errors.
I think my state for opening and closing the ShipsList, so that's OpenshipsList and CloseshipsList, needs to be handled better.
How can I better deal with this?
Does anyone have any ideas that may help me solve this problem and make it work.
The following code is from my CruiseListHeader component
import React from 'react'
import {getCruiseLines } from '../api/api';
class CruiseListHeader extends React.Component {
constructor(props) {
super(props)
//setting intial state for cruise heading and shipsList and initialize cruiseHeaders as an empty array
this.state = {
cruiseHeaders: [],
shipsList: {isOpen:false}
}
//binding methods for Cruise Line Headers and Handle Click Function
this.setUpCruiseLines = this.setUpCruiseLines.bind(this),
this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
console.log('cdm')
this.setUpCruiseLines()
}
setUpCruiseLines() {
console.log('getcruiselines')
getCruiseLines()
.then(res => {
this.setState({
cruiseHeaders: res
})
})
}
/* There will be Headings for all the Cruise Lines.
When a Cruise Line Heading is clicked, it goes to ListofShips Component and the Ships List opens up for that Heading.
When user clicks on a Cruise Line Heading, when a Ships List is open, the Ships List Collapses.*/
handleClick(event) {
// Maybe do a ternary operator here before open and close functions
this.state.shipsList === isOpen ? OpenShipsList : CloseshipsList
OpenshipsList(event) {
this.setState = {shipsList: {isOpen:true}}
return
<div>
<ListofShips/>
</div>
}
CloseshipsList(event) {
this.setState = {shipsList: {isOpen: false}}
render()
}
}
// This renders at the start when the page loads and also when you close a list
render() {
return (
<React.Fragment>
<h3><button onClick = {this.handleClick}>{ship.cruise_line}</button></h3>
</React.Fragment>
)
}
}
export default CruiseListHeader
At the moment, when I do a yarn dev I am getting the following error
ERROR in ./client/components/CruiseListHeader.jsx Module build failed:
SyntaxError: Unexpected token, expected ; (42:29)
I would like to get rid of this error and display the page like I have described above.
As a beginning, to set isOpen correctly on the state, modify the onClick function handler as this:
handleClick(event) {
// this handleClick function should only handle the `isOpen` value in the state.
// Any renders supposibly to be made on the `render` method instead.
this.setState(prevState => ({
shipsList: {
isOpen: !prevState.shipsList.isOpen, //will reverse the prevState of isOpen.
}
}));
}
Now, Going to your render, we can handle the way you renderthe component that depends on the this.state.shipsList.isOpen this way:
render() {
//destructive declaration for isOpen from inside the shipsList in the state.
const { shipsList: { isOpen } } = this.state;
return (
<React.Fragment>
<h3>
<button onClick={this.handleClick}>
{ship.cruise_line}
</button>
</h3>
{
// Usually modals are shown at the bottom of the render return.
// it's better to use tenary `val ? component : null` rather than: (val && component)
// React accepts a component, or a null as return value, the second will return false if val was false.
isOpen ? <OpenShipsList /> : null
}
</React.Fragment>
)
}
PS: Please follow the comments inside the code above of each line, they should be enough illustrating what happened, if something was ambiguos, just let me know.
Hard to tell with the indentations, but is this.state.shipsList === isOpen ? OpenShipsList : CloseshipsList supposed to really be this.state.shipsList.isOpen ? OpenShipsList() : CloseshipsList();? Note that isOpen is a property of state.shipsList, and then the parens to invoke the calls to open/close the list, and also the semi-colon to end the line.
I think you probably really want your handleClick to simply toggle the open state and then use that state value to selectively render the list.
const handleClick = event => this.setState(
prevState => ({ shipsList: {isOpen: !prevState.shipsList.isOpen} })
);
render() {
const { shipsList: { isOpen } } = this.state;
return (
<Fragment>
{isOpen && <ListofShips />}
<h3>
<button onClick={this.handleClick}>{ship.cruise_line}</button>
</h3>
</Fragment>
)
}

React.js, correct way to iterate inside DOM

Im new in ReactJS...
I have a project with the following class components structure:
index.js
--app
--chat
--header
--left
--right
In the chat.js component, I make a google search with the api to retrieve images based on specific keyword... My intuitive solution was:
this.client.search("cars")
.then(images => {
for(let el of images) {
ReactDOM.render(<img src="{{el.url}}" syle="{{width: '100%'}}" />, document.querySelector('#gimages'));
}
});
It is correct? Or I may to use Components with stored states with flux (redux)?
Perhaps a simpler more conventional use of react would achieve what your require?
You could follow a pattern similar to that shown below to achieve what you require in a more "react-like" way:
class Chat extends React.Component {
constructor(props) {
super(props)
this.state = { images : [] } // Set the inital state and state
// model of YourComponent
}
componentDidMount() {
// Assume "client" has been setup already, in your component
this.client.search("cars")
.then(images => {
// When a search query returns images, store those in the
// YourComponent state. This will trigger react to re-render
// the component
this.setState({ images : images })
});
}
render() {
const { images } = this.state
// Render images out based on current state (ie either empty list,
// no images, or populated list to show images)
return (<div>
{
images.map(image => {
return <img src={image.url} style="width:100%" />
})
}
</div>)
}
}
Note that this is not a complete code sample, and will require you to "fill in the gaps" with what ever else you have in your current Chat component (ie setting up this.client)
This is not the way you should go, you don't need to use ReactDOM.render for each item. Actually, you don't need to use ReactDOM.render at all. In your component you can use a life-cycle method to fetch your data, then set it to your local state. After getting data you can pass this to an individual component or directly render in your render method.
class Chat extends React.Component {
state = {
images: [],
}
componentDidMount() {
this.client.search( "cars" )
.then( images => this.setState( { images } ) );
}
renderImages = () =>
this.state.images.map( image => <Image key={image.id} image={image} /> );
render() {
return (
<div>{this.renderImages()}</div>
);
}
}
const Image = props => (
<div>
<img src={props.image.url} syle="{{width: '100%'}}" />
</div>
);
At this point, you don't need Redux or anything else. But, if you need to open your state a lot of components, you can consider it. Also, get being accustomed to using methods like map, filter instead of for loops.

How to search from React component rendered HTML string?

Hi I am working on a react-based table, which should support search functionality. Pseudo code like below:
export default class MyTable {
constructor() {
this.onSearch = (event) => {
const value = event.target.value;
/* stuck here */
}
}
return (
<MySearch onSearch={this.onSearch} />
<MyTableBody />
);
}
MyTableBody is responsible to translate text into <tr><td> HTML elements. I am stuck at two points:
How can I get MyTableBody rendered HTML string so that I can use it in
this.onSearch method?
How to retrieve text only from HTML string?
Just like the text() method used in jest/enzyme.
What you did was what we would normally do with JQuery.
With React, it's bit different.
export default class MyTable {
constructor() {
super();
this.state = {
keyword: undefined
}
}
onSearch = (event) => {
this.setState({keyword: event.target.value});
}
render() {
return (
<MySearch onSearch={this.onSearch.bind(this)} />
<MyTableBody searchKeyword={this.state.keyword} />
)
}
}
"onSearch" should be called when you typing in the textbox inside 'MySearch' component.
Now 'searchKeyword' prop gets updated in the MyTableBody component through the state variable 'keyword'
Then you must have a filter method in the MyTableBody component to filter the Data object/array used to render the table.
I have not tested this but concept should be like that
You should change the way you think when working with React.
We should use the re-rendering functionality to do the DOM manipulation.
You never have to use something like JQuery ever again.

state props overridden by plugin props

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>...
)
}

Resources