React function not working from child component - reactjs

I am trying to get a function working which removes an image uploaded using React Dropzone and react-sortable.
I have the dropzone working, and the sort working, but for some reason the function I have on the sortable item which removes that particular item from the array does not work.
The onClick event does not seem to call the function.
My code is below.
const SortableItem = SortableElement(({value, sortIndex, onRemove}) =>
<li>{value.name} <a onClick={() => onRemove(sortIndex)}>Remove {value.name}</a></li>
);
const SortableList = SortableContainer(({items, onRemove}) => {
return (
<ul>
{items.map((image, index) => (
<SortableItem key={`item-${index}`} index={index} value={image} sortIndex={index} onRemove={onRemove} />
))}
</ul>
);
});
class renderDropzoneInput extends React.Component {
constructor (props) {
super(props)
this.state = { files: [] }
this.handleDrop = this.handleDrop.bind(this)
}
handleDrop (files) {
this.setState({
files
});
this.props.input.onChange(files)
}
remove (index){
var array = this.state.files
array.splice(index, 1)
this.setState({files: array })
this.props.input.onChange(array)
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState({
files: arrayMove(this.state.files, oldIndex, newIndex),
});
};
render () {
const {
input, placeholder,
meta: {touched, error}
} = this.props
return (
<div>
<Dropzone
{...input}
name={input.name}
onDrop={this.handleDrop}
>
<div>Drop your images here or click to open file picker</div>
</Dropzone>
{touched && error && <span>{error}</span>}
<SortableList items={this.state.files} onSortEnd={this.onSortEnd} onRemove={(index) => this.remove(index)} />
</div>
);
}
}
export default renderDropzoneInput

Update: This was caused by react-sortable-hoc swallowing click events. Setting a pressDelay prop on the element allowed the click function to fire.

This is old question, but some people, like me, who still see this issue, might want to read this: https://github.com/clauderic/react-sortable-hoc/issues/111#issuecomment-272746004
Issue is that sortable-hoc swallows onClick events as Matt found out. But we can have workarounds by setting pressDelay or distance.
For me the best option was to set minimum distance for sortable list and it worked nicely
You can also use the distance prop to set a minimum distance to be dragged before sorting is triggered (for instance, you could set a distance of 1px like so: distance={1})

Related

React, passed state is not changing in other component

I started with react and have a small problem
export default function TestComponent3({ typeId}) {
console.log('CHANGING HERE', { typeId});
const handleClick = () => {
console.log('NOT CHANGING HERE', { typeId});
console.log('NOT CHANGING HERE EITHER', typeId);
};
return (
<>
<Spectrum.Button variant="secondary" onClick={handleClick}>
CHANGING HERE {typeId}
</Spectrum.Button>
</>
);
}
In the other component the state of 'myProp' is changing by the UI dropdown.
Spectrum.Button content changes dynamically but console logs are stuck on default state option.
I probably messed something up and and It's easy fix.
EDIT://
Sibling with a dropdown
export default function TypeForm({ typeId, setTypeId }) {
return (
<div>
<Spectrum.Dropdown className="dropdown" placeholder="Choose type...">
<Spectrum.Menu onChange={setTypeId} slot="options">
{Types.map(type => { //types 0,1,2,3,4,5,6,7,8
return (
<Spectrum.MenuItem selected={typeId === type.id ? true : null} key={type.id} className="jsontest">
{type.name}
</Spectrum.MenuItem>
);
})}
</Spectrum.Menu>
</Spectrum.Dropdown>
)}
</div>
);
}
And Parent
export default function Form() {
const [typeId, setTypeId] = useState(0);
const setTypeIdFunc = e => {
setTypeId(e.target.selectedIndex);
};
return (
<TestComponent3 typeId={typeId} />
<TypeForm
typeId={typeId}
setTypeId={setTypeIdFunc}
/>
)
The props are passed down first from the parent component of TestComponent3 during the first render.
This means you would have to send handleClick from one "higher" component. Or use a shared state-like context provider.
I fixed it by changing Spectrum.Button to normal button.

autosuggest not showing item immediately

I am looking into fixing a bug in the code. There is a form with many form fields. Project Name is one of them. There is a button next to it.So when a user clicks on the button (plus icon), a popup window shows up, user enters Project Name and Description and hits submit button to save the project.
The form has Submit, Reset and Cancel button (not shown in the code for breviety purpose).
The project name field of the form has auto suggest feature. The code snippet below shows the part of the form for Project Name field.So when a user starts typing, it shows the list of projects
and user can select from the list.
<div id="formDiv">
<Growl ref={growl}/>
<Form className="form-column-3">
<div className="form-field project-name-field">
<label className="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated custom-label">Project Name</label>
<AutoProjects
fieldName='projectId'
value={values.projectId}
onChange={setFieldValue}
error={errors.projects}
touched={touched.projects}
/>{touched.projects && errors.v && <Message severity="error" text={errors.projects}/>}
<Button className="add-project-btn" title="Add Project" variant="contained" color="primary"
type="button" onClick={props.addProject}><i className="pi pi-plus" /></Button>
</div>
The problem I am facing is when some one creates a new project. Basically, the autosuggest list is not showing the newly added project immediately after adding/creating a new project. In order to see the newly added project
in the auto suggest list, after creating a new project,user would have to hit cancel button of the form and then open the same form again. In this way, they can see the list when they type ahead to search for the project they recently
created.
How should I make sure that the list gets immediately updated as soon as they have added the project?
Below is how my AutoProjects component looks like that has been used above:
import React, { Component } from 'react';
import Autosuggest from 'react-autosuggest';
import axios from "axios";
import { css } from "#emotion/core";
import ClockLoader from 'react-spinners/ClockLoader'
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// Use your imagination to render suggestions.
const renderSuggestion = suggestion => (
<div>
{suggestion.name}, {suggestion.firstName}
</div>
);
const override = css`
display: block;
margin: 0 auto;
border-color: red;
`;
export class AutoProjects extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
projects: [],
suggestions: [],
loading: false
}
this.getSuggestionValue = this.getSuggestionValue.bind(this)
this.setAutoSuggestValue = this.setAutoSuggestValue.bind(this)
}
// Teach Autosuggest how to calculate suggestions for any given input value.
getSuggestions = value => {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp(escapedValue, 'i');
const projectData = this.state.projects;
if (projectData) {
return projectData.filter(per => regex.test(per.name));
}
else {
return [];
}
};
// When suggestion is clicked, Autosuggest needs to populate the input
// based on the clicked suggestion. Teach Autosuggest how to calculate the
// input value for every given suggestion.
getSuggestionValue = suggestion => {
this.props.onChange(this.props.fieldName, suggestion.id)//Update the parent with the new institutionId
return suggestion.name;
}
fetchRecords() {
const loggedInUser = JSON.parse(sessionStorage.getItem("loggedInUser"));
return axios
.get("api/projects/search/getProjectSetByUserId?value="+loggedInUser.userId)//Get all personnel
.then(response => {
return response.data._embedded.projects
}).catch(err => console.log(err));
}
setAutoSuggestValue(response) {
let projects = response.filter(per => this.props.value === per.id)[0]
let projectName = '';
if (projects) {
projectName = projects.name
}
this.setState({ value: projectName})
}
componentDidMount() {
this.setState({ loading: true}, () => {
this.fetchRecords().then((response) => {
this.setState({ projects: response, loading: false }, () => this.setAutoSuggestValue(response))
}).catch(error => error)
})
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
// Autosuggest will call this function every time you need to update suggestions.
// You already implemented this logic above, so just use it.
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: this.getSuggestions(value)
});
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render() {
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: value,
value,
onChange: this.onChange
};
// Finally, render it!
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
<div className="sweet-loading">
<ClockLoader
css={override}
size={50}
color={"#123abc"}
loading={this.state.loading}
/>
</div>
</div>
);
}
}
The problem is you only call the fetchRecord when component AutoProjects did mount. That's why whenever you added a new project, the list didn't update. It's only updated when you close the form and open it again ( AutoProjects component mount again)
For this case I think you should lift the logic of fetchProjects to parent component and past the value to AutoProjects. Whenever you add new project you need to call the api again to get a new list.

Material ui pickers, how to prevent closing on redraw

I have a picker which is a part of a component where the date is given from another, super, component.
If the super component is redrawn (say due to a date change) the component that holds the datepicker is destroyed and recreated, thus losing the "open" state.
How would I make this managed? - I can manually state if a picker is "open" or not. However I cannot seem to hook up to the "click on date bar" or "click outside calendar", thus I cannot actually control this.
The component containing the picker:
export class DateDisplay extends React.Component {
render() {
const {date, onChange, onAccept, onOpen, label, openState} = this.props;
return <>
{label && <Typography variant={'subtitle2'} display={'inline'} style={{marginRight: '1ch'}}>
{label}
</Typography>}
<DatePicker
value={date}
onOpen={() => {
this.setState({open: true},onOpen)
}}
onChange={onChange}
onAccept={onAccept}
format={'ddd, D MMMM YYYY'}
variant={'inline'}
open={openState}
onClick={e => {
console.log('we clicked the picker')
}}
/>
</>
}
}
And the super component which would be responsible for "managing" the datepicker:
export class DatesList extends React.Component {
this.onSave = async () {/*code to request a save to the backend*/};
onStartEditing = () {/*code to visually indicate we're editing a field*/};
render() {
const {dates} = this.props; //date is a moment + meta data.
const sorted_dates = Array.from(eventData.dates.values()).sort(
(l, r) =>
(!l.start_date ? 0 : l.start_date.valueOf()) - (!r.start_date ? 0 : r.start_date.valueOf())
);
sorted_dates.map(date => {<div>
<DateDisplay
label={'Event start date:'}
date={date.start_date}
onOpen={(e) => {
this.onStartEditing(e, 'start_date');
}}
onChange={(newDate) => {
date.start_date = !nullOrUndefined(newDate) ? newDate.toDate() : newDate;
}}
onAccept={async () => {
await this.onSave('start_date');
}}
/>
</div>});
}
}
As a side question: I notice that the components are not rebuild if the sorting is removed, even though there is a single element in the list thus sorting would always give the same "order", why's that?
You can use your own state for opening/closing.
Use open prop and onClose and onOpen callbacks. Deeper example from documentation: https://material-ui-pickers.dev/guides/controlling-programmatically

Filter state that is set from onClick as data- in react

Two main issues.
(1) onClick needs to update two items in my state
(2) I need to filter state #2 to count the number of times a string appears and render it if it equals the length of state#1
More Detail:
I am mapping through my objects and rendering a button for each. I need the onClick to setState of two different attributes so i pass value={item.item} to update state selectedItems , and data-matches={item.matches} to update state of matchesList.
-- note that item.item is a string and item.matches is an array of strings.
When I call onClick to update the state, the value works fine, but the data-matches creates a weird object which i cant iterate over and this is why thats a problem...
I need to map through the data-matches state and count each instance of a string, if the count of that particular string is equal to the length of selectedItems state, then I need to render that.
If this is confusing to you, it's because I am completely lost and new to this. Maybe its worth mentioning that my props are coming from Redux..
Example of some objects for reference
{
'item': 'apple',
'matches': ['beef', 'bacon', 'cheese', 'carrot'],
},
{
'item': 'carrot',
'matches': ['apple', 'bacon', 'goat'],
},
export class Items extends Component {
constructor(props) {
super(props);
this.state = {
selectedItems: [],
firstStep: true,
matchesList: []
};
this.selectItems = this.selectItems.bind(this);
this.filterMatches = this.filterMatches.bind(this);
}
selectItems(event) {
this.setState({
selectedItems: [...this.state.selectedItems, event.target.value],
firstStep: false,
matchesList: [...this.state.matchesList, event.target.dataset.matches]
});
}
Below, in the col 'All", I am mapping through my state selectedItems which is an array, so I can grab the actual object from props which is the same name. I then map over the matches array of that object, and grab the actual object that has the same name as the matches...
in the col 'Cross Referenced", I am mapping over the state updated from the onClick. This is where I need to filter.. I'll show you my attempt at that after this code block.
render() {
return (
<Fragment>
<div className="col">
<div className="row">
<div className="col-5">
<h1>All</h1>
{this.state.selectedItems.map(selected =>
this.props.items.map(item =>
item.item == selected
? item.matches.map(match =>
this.props.items.map(item =>
item.item == match ? (
<div>
<p>{item.item}</p>
<button
key={item.item}
value={item.item}
data-matches={item.matches}
onClick={this.selectItems}
>
Select
</button>
</div>
) : null
)
)
: null
)
)}
</div>
<div className="col-5">
<h1>Cross Referenced</h1>
{this.state.matchesList.map(matches => (
<p>{matches}</p>
))}
</div>
</div>
</div>
)}
</div>
</Fragment>
);
}
}
My attempt at filtering, even though the matchesList is not right.
filterMatches(matchesList, selectedItems) {
let arr1 = matchesList;
let arr2 = selectedItems;
let obj = arr1.reduce((op, inp) => {
let key = inp;
op[key] = op[key] || 0;
op[key]++;
return op;
}, {});
let final = Object.keys(obj).filter(key => {
return obj[key] === arr2.length;
});
return final;
}
in the render
<div className="col-5">
<h1>cross referenced</h1>{this.state.matchesList.filter(this.filterMatches(this.state.matchesList, this.state.selectedItems)).map(matches => (
<p>{matches}</p>
))}
</div>
</div>
I tried out the data-matches thing, and it looks to me like event.target.dataset.matches was coming out to be a string (Not an array of strings, one big CSV string). Try doing it the following way:
class YourComponent extends React.Component {
state = {...};
selectItems = (item, matches) => {
this.setState({
selectedItems: [...this.state.selectedItems, item],
firstStep: false,
matchesList: [...this.state.matchesList, ...matches]
});
}
render() {
return (
{...}
{this.props.items.map(item => (
<button
key={item.item}
value={item.item}
onClick={() => this.selectItems(item.item, item.matches)}
>
Select
</button>
))}
{...}
);
}
}
Actually I think I see what your issue is, the filter is working fine and it's already giving you your array of ["bacon"]. Try getting rid of the outer filter, I don't see a point to it being there.
const YourComponent = props => (
{...}
{this.filterMatches(this.state.matchesList, this.state.selectedItems).map(matches => (
<p>{matches}</p>
))}
{...}
);

react-sortable-hoc: Handling of click event on list item

This question is about https://github.com/clauderic/react-sortable-hoc.
I am pretty new to React, so I find the following a bit irritating. Go to
https://jsfiddle.net/7tb7jkz5/. Notice line 4
const SortableItem = SortableElement(({value}) => <li className="SortableItem" onClick={console.debug(value)}>{value}</li>);
When you run the code, the console will log "Item 0" to "Item 99". A click on an item will log the same, but three times. Why does this happen? And is this really necessary or more like a bug?
I expected a behavior similar to an ordinary DOM, so the click event would bubble up from the clicked item and could be caught along the way through its ancestors. The observed behavior looks to me like the click event would be sent down by the list to each list item three times.
Update:
Well, this is actually as clear as crystal, thanks for pointing this out, Shubham. I did not just specify a reference, but an actual call to console.debug, which was executed every time the expression was evaluated. Common mistake.
Still, this means, each list item is rendered (I guess) three times, when I click one of them. I suspect missing optimization or even useless redrawing.
Try to use distance={1} on SortableContainer component.
Check this link https://github.com/clauderic/react-sortable-hoc/issues/461
const ListItemContainer = SortableContainer((props) => {
return <listItem />
})
<ListItemContainer
onSortEnd={this._orderingFolder}
lockAxis='y'
lockToContainerEdges={true}
lockOffset='0%'
distance={1}
/>
Another way you can define and handle click event using react-sortable-hoc:
please check below code
import { SortableContainer, SortableElement, arrayMove } from 'react-sortable-hoc';
const SortableItem = SortableElement(({ value }) => {
return (
<div >
<div id="button_value">{value}</div>
</div >
);
});
const SortableList = SortableContainer(({ items }) => {
return (
<div >
{items.map((value, index) => (
<SortableItem
key={`item-${index}`}
index={index}
value={value}
/>
))}
</div>
);
});
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'],
};
}
onSortEnd = ({ oldIndex, newIndex }) => {
this.setState(({ items }) => ({
items: arrayMove(items, oldIndex, newIndex),
}));
};
shouldCancelStart = (e) => {
var targetEle = e;
if (!targetEle.id) {
targetEle = e.target;
}
if (targetEle.id === 'button_value') {
console.log('Div button click here ');
// perform you click opration here
}
// you can define multiple click event here using seprate uniq id of html element even you can manage delay on click here also using set timeout and sortmove props and sortend props you need to manage just one boolean flag.
}
render() {
return (
<div>
<SortableList
items={this.state.items}
onSortEnd={this.onSortEnd}
onSortStart={(_, event) => event.preventDefault()}
shouldCancelStart={this.shouldCancelStart} />
</div>
);
}
}
export default App;
First define id button_value as in html element and then using this id you can get click event using this props shouldCancelStart
You need to mention the onClick action as a function. Use () => inside the handler call. Try the below method, it works although has a very slow response
const SortableItem = SortableElement(({value}) => <li className="SortableItem" onClick={() => console.debug(value)}>{value}</li>);

Resources