Related
I try to build sliders with different categories that each user has his point.
The informant comes from the json server
What I need I do not succeed in having the customer choose a user that is numbered and the dot will be colored in the slider How do I do that?
In addition he has the option to delete and return the point.
I was able to delete the points by deleting them in the object. But I could not return, is there a possibility to return?
Broker.jsx
import React, { useEffect, useState } from 'react';
import './style.css';
import Combo from '../components/Combo/Combo';
import Sliders from '../components/Sliders/Sliders';
const GetUsersDataFromManipulation = (users, field) => {
const state = users.reduce((store, user) => {
const userId = user.user
const currentManipulationUserData = user.profileManipulation[field]
if (currentManipulationUserData.length === 0) {
return store
}
store[userId] = currentManipulationUserData[0].bid
return store;
}, {})
return state;
};
function Broker({ manipulations }) {
const users = manipulations[2].users
const [hiddenUser, setHiddenUser] = useState(() => {
const visible = {};
for (let user of users) {
visible[user.user] = true;
}
return visible;
})
const GetUsersBid = (profileManipulation) => {
const data = GetUsersDataFromManipulation(users, `${profileManipulation}`); if (!Object.keys(data).length) {
return null
}
return data;
};
const gender = GetUsersBid('gender');
const age = GetUsersBid('age');
const marital = GetUsersBid('marital');
const children = GetUsersBid('children');
const education = GetUsersBid('education');
const interests = GetUsersBid('interests');
const dynamicInterests = GetUsersBid('dynamicInterests');
const showUser = (user_id) => {
const new_hidden = { ...hiddenUser }
new_hidden[user_id] = true;
setHiddenUser(new_hidden);
}
const hideUser = (user_id) => {
const new_hidden = { ...hiddenUser }
console.log(user_id)
new_hidden[user_id] = false;
setHiddenUser(new_hidden);
}
const [userInformation, setUserInformation] = useState([
{ name: 'gender', bids: gender },
{ name: 'age', bids: age },
{ name: 'marital', bids: marital },
{ name: 'children', bids: children },
{ name: 'education', bids: education },
{ name: 'interests', bids: interests },
{ name: 'dynamicInterests ', bids: dynamicInterests },
]);
useEffect(() => {
const curret_User_Info = [...userInformation]
for (let user of Object.keys(hiddenUser)) {
for (let i = 0; i < curret_User_Info.length; i++) {
if (curret_User_Info[i].bids !== null) {
if (hiddenUser[user] === false) {
delete curret_User_Info[i].bids[user]
}
else {
//What am I returning here? So that the bids will return?
}
}
}
}
setUserInformation(curret_User_Info)
}, [hiddenUser])
return (
<div>
<div className="button" >
{userInformation && <Combo users={users} showUser={showUser} hideUser={hideUser} userInformation={userInformation} />}
</div>
<div className='slid'>
{userInformation.map(sliderDetails => {
return (
<div className={sliderDetails.name} key={sliderDetails.name} >
{sliderDetails.bids && (<Sliders className="sliders" hiddenUserChange={hiddenUser} name={sliderDetails.name} userBids={sliderDetails.bids} setUserInformation={setUserInformation} userInformation={userInformation} />)}
</div>
)
})}
</div>
</div>
);
}
export default Broker;
ComboBox.jsx
import React, { useEffect, useRef, useState } from 'react';
import ComboBox from 'react-responsive-combo-box';
import { Button } from '#mui/material';
import 'react-responsive-combo-box/dist/index.css';
import "./style.css"
function Combo({ users, showUser, hideUser, userInformation }) {
const [selectedOption, setSelectedOption] = useState();
const [choosing, setChoosing] = useState();
useEffect(() => {
}, [users])
const onShow = () => {
showUser(users[selectedOption - 1].user)
}
const onHide = () => {
hideUser(users[selectedOption - 1].user)
}
const colorChange = (numOption) => {
const id = users[numOption - 1].user
}
return (
<div className="combo_box">
<ComboBox
onSelect={(option) => { setSelectedOption(option); colorChange(option) }}
options={[...Array.from({ length: users.length }, (_, i) => i + 1)]}
/>
<div className='button' >
<Button style={{ "marginRight": 20 }} variant="contained" onClick={onShow}>Show</Button>
<Button variant="contained" onClick={onHide}>Hide</Button>
</div>
</div>
);
}
export default Combo;
Sliders.jsx
import React, { useEffect, useState } from 'react'
import "./style.css"
import 'rc-slider/assets/index.css';
import Slider from 'rc-slider';
const Sliders = ({ hiddenUserChange, name, userBids, setUserInformation, userInformation }) => {
const [bids, setBids] = useState()
useEffect(() => {
setBids(Object.values(userBids))
}, [hiddenUserChange, userBids])
const updateFieldChanged = (newValue, e) => {//OnChanged Slider
setUserInformation(state => {
return state.map(manipulation => {
if (manipulation.name === name) {
Object.entries(manipulation.bids).forEach(([userId, bidValue], index) => {
manipulation.bids[userId] = newValue[index]
console.log(manipulation.bids[userId])
})
}
return manipulation
})
});
}
const handleChange = (event, newValue) => {
setBids(event)
};
return (
<>
<h1 className='headers'>{name}</h1>
{
<Slider
style={{ "marginRight": "20rem", "width": "30rem", "left": "20%" }}
range={true}
trackStyle={[{ backgroundColor: '#3f51b5' }]}
max={100}
RcSlider={true}
railStyle={{ backgroundColor: '#3f51b5' }}
activeDotStyle={{ left: 'unset' }}
ariaLabelForHandle={Object.keys(hiddenUserChange)}
tabIndex={(Object.keys(userBids))}
ariaLabelledByForHandle={bids}
value={(bids)}
onChange={handleChange}
onAfterChange={updateFieldChanged}
tipProps
tipFormatter
/>
}
</>
)
}
export default Sliders
enter image description here
Thank you all!
I am trying to implement TABS in my demo application.
condition : I want to render those TABS or menu item which have length less than 100px
I created tab with following steps
export const TabsContext = React.createContext({
selectedTabId: "",
setSelectedTabId: () => {},
tabsVisible: [],
tabsHidden: [],
setTabsVisible: () => {},
setTabsHidden: () => {}
});
Created a context which have selectedIndex, visibleArray, hiddenArray is present
In TABLIST component I checked all element width .if it is greater than 100 I pushed to visible else hidden.
but how to render visible array/ ITEM
const TabList = ({
isAlign,
isSize,
isBoxed,
isToggle,
isToggleRounded,
isFullwidth,
children,
...rest
}) => {
const tabsRef = useRef(null);
const { tabsVisible, tabsHidden, setTabsVisible, setTabsHidden } = useContext(
TabsContext
);
const getWidth = () => {
let hidden = [],
visible = [];
[...tabsRef.current.children].forEach((element) => {
console.log(element.offsetWidth);
if (element.offsetWidth > 100) {
hidden.push(element);
} else {
visible.push(element);
}
});
};
useEffect(() => {
getWidth();
}, []);
return (
<div>
<ul className="tabs" ref={tabsRef}>
{children}
</ul>
</div>
);
};
TabList.displayName = "Tabs.TabList";
export { TabList };
here is my whole code
https://codesandbox.io/s/intelligent-wescoff-dd3xd?file=/src/tablist.js:141-924
Getting Error while rendering
Objects are not valid as a React child (found: [object HTMLLIElement]).
I set the default value of visible array
<Tabs
defaultTabId="1"
defaultTabsVisible={data.map((i) => (
<Tabs.Tab tabId={i} key={i}>
{i}
</Tabs.Tab>
))}
>
<Tabs.TabList isSize="medium"></Tabs.TabList>
</Tabs>
and try to updated array
const getWidth = () => {
let hidden = [],
visible = [];
[...tabsRef.current.children].forEach((element) => {
console.log(element.offsetWidth);
if (element.offsetWidth > 100) {
hidden.push(element);
} else {
visible.push(element);
}
});
setTabsVisible(visible);
setTabsHidden(hidden);
};
while update I am getting this error
Objects are not valid as a React child (found: [object HTMLLIElement]). I
Ok so this is what I came up with
import React, { useRef, useContext, useEffect, useState } from "react";
import classNames from "classnames";
import { TabsContext } from "./context";
const TabList = ({
isAlign,
isSize,
isBoxed,
isToggle,
isToggleRounded,
isFullwidth,
children,
...rest
}) => {
const tabsRef = useRef(null);
const { tabsVisible, tabsHidden, setTabsVisible, setTabsHidden } = useContext(
TabsContext
);
const [firstRender, setFirstRender] = useState(true);
const getWidth = () => {
let hidden = [],
visible = [];
[...tabsRef.current.children].forEach((element, i) => {
console.log(element.offsetWidth);
if (element.offsetWidth > 100) {
hidden.push(children[i]);
} else {
visible.push(children[i]);
}
});
setTabsVisible(visible);
setTabsHidden(hidden);
if(tabsRef.current)
setFirstRender(false)
};
useEffect(() => {
getWidth();
}, []);
return (
<div>
<ul className="tabs" ref={tabsRef}>
{firstRender ? children : tabsVisible }
</ul>
</div>
);
};
TabList.displayName = "Tabs.TabList";
export { TabList };
This will check all children size on initial render and update the visible array. Then after the first render it will only display the visible children.
Update - aria-hidden
{firstRender ?
(<ul className="tabs" ref={tabsRef} aria-hidden={false}>
{children}
</ul>)
:
(<>
<ul className="tabs" ref={tabsRef} aria-hidden={false}>
{tabsVisible}
</ul>
<ul className="tabs-hidden" ref={tabsRef} aria-hidden={true}>
{tabsHidden}
</ul>
</>)
}
If you want aria-hidden property to be individual for each tab, you would have to check for element width inside the tab component also. Then add the aria-hidden hidden property in there.
You have to use the property opacity of CSS to hide your tabs, after this step you have to create a state tabsToDisplay to store the tabs that verify your condition.
styles.css
.tabs-hidden {
opacity: 0; // <=== hide your tabs
display: flex;
list-style: none;
}
tabList.js
import React, { useRef, useContext, useEffect, useState } from "react";
import classNames from "classnames";
import { TabsContext } from "./context";
const TabList = ({
isAlign,
isSize,
isBoxed,
isToggle,
isToggleRounded,
isFullwidth,
children,
...rest
}) => {
const tabsRef = useRef(null);
const { tabsVisible, tabsHidden, setTabsVisible, setTabsHidden } = useContext(
TabsContext
);
const [tabsToDisplay, setTabsToDisplay] = useState([]); // <=== create this state to store the tabs that match your condition
const getWidth = () => {
let hidden = [],
visible = [],
tabsToDisplay = [];
[...tabsRef.current.children].forEach((element, i) => {
if (element.offsetWidth > 100) {
hidden.push(element);
} else {
visible.push(element);
tabsToDisplay.push(children[i]);
}
});
console.log("test", tabsToDisplay);
setTabsToDisplay(tabsToDisplay);
};
useEffect(() => {
getWidth();
}, []);
return (
<div>
<ul className="tabs-hidden" ref={tabsRef}>
{children}
</ul>
<ul className="tabs">{tabsToDisplay}</ul>
</div>
);
};
TabList.displayName = "Tabs.TabList";
export { TabList };
You can check the demo:
https://codesandbox.io/s/hungry-galileo-qk09i?file=/src/tablist.js:0-1156
As you're using ref, you can do it using them.
This is an alternative to current answers, using useLayoutEffect
const TabList = ({
isAlign,
isSize,
isBoxed,
isToggle,
isToggleRounded,
isFullwidth,
children,
...rest
}) => {
const tabsRef = useRef(null);
const { tabsVisible, tabsHidden, setTabsVisible, setTabsHidden } = useContext(
TabsContext
);
React.useLayoutEffect(() => {
let hidden = [],
visible = [];
[...tabsRef.current.children].forEach((element) => {
console.log(element.offsetWidth);
if (element.offsetWidth > 100) {
element.style.display = "none";
element.setAttribute("aria-hidden", "true");
hidden.push(element);
} else if (element.style.display !== 'none') {
element.style.display = "block";
element.setAttribute("aria-hidden", "false");
visible.push(element);
}
});
setTabsVisible(visible)
setTabsHidden(hidden)
}, [setTabsVisible, setTabsHidden, tabsRef])
return (
<div>
<ul className="tabs" ref={tabsRef}>
{children}
</ul>
</div>
);
};
Changed tabs to prevent infinite rerendering
return React.createElement(
TabsContext.Provider,
{
value: {
selectedTabId,
setSelectedTabId,
tabsVisible,
tabsHidden,
setTabsVisible,
setTabsHidden
}
},
children
);
Also note the
} else if (element.style.display !== "none") {
This prevents the element that is displayed with none, to be displayed as having width < 100 on a further rerender
https://codesandbox.io/s/pedantic-kirch-ykdc3?file=/src/tablist.js
I am trying to use React Scheduler with my shifts database. The current state after trying to use hooks instead of class is that I cannot edit any field in the form. I have deleted some of the code to make it cleaner, for now I am trying only to add a shift.
React Scheduler original code:
import * as React from 'react';
import Paper from '#material-ui/core/Paper';
import { ViewState, EditingState } from '#devexpress/dx-react-scheduler';
import {
Scheduler,
Appointments,
AppointmentForm,
AppointmentTooltip,
WeekView,
} from '#devexpress/dx-react-scheduler-material-ui';
import { appointments } from '../../../demo-data/appointments';
export default class Demo extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
data: appointments,
currentDate: '2018-06-27',
addedAppointment: {},
appointmentChanges: {},
editingAppointment: undefined,
};
this.commitChanges = this.commitChanges.bind(this);
this.changeAddedAppointment = this.changeAddedAppointment.bind(this);
this.changeAppointmentChanges = this.changeAppointmentChanges.bind(this);
this.changeEditingAppointment = this.changeEditingAppointment.bind(this);
}
changeAddedAppointment(addedAppointment) {
this.setState({ addedAppointment });
}
changeAppointmentChanges(appointmentChanges) {
this.setState({ appointmentChanges });
}
changeEditingAppointment(editingAppointment) {
this.setState({ editingAppointment });
}
commitChanges({ added, changed, deleted }) {
this.setState((state) => {
let { data } = state;
if (added) {
const startingAddedId = data.length > 0 ? data[data.length - 1].id + 1 : 0;
data = [...data, { id: startingAddedId, ...added }];
}
return { data };
});
}
render() {
const {
currentDate, data, addedAppointment, appointmentChanges, editingAppointment,
} = this.state;
return (
<Paper>
<Scheduler
data={data}
height={660}
>
<ViewState
currentDate={currentDate}
/>
<EditingState
onCommitChanges={this.commitChanges}
addedAppointment={addedAppointment}
onAddedAppointmentChange={this.changeAddedAppointment}
appointmentChanges={appointmentChanges}
onAppointmentChangesChange={this.changeAppointmentChanges}
editingAppointment={editingAppointment}
onEditingAppointmentChange={this.changeEditingAppointment}
/>
<WeekView
startDayHour={9}
endDayHour={17}
/>
<Appointments />
<AppointmentTooltip
showOpenButton
showDeleteButton
/>
<AppointmentForm />
</Scheduler>
</Paper>
);
}
}
My function component code:
import React, { useState } from 'react';
import Paper from '#material-ui/core/Paper';
import { ViewState, EditingState } from '#devexpress/dx-react-scheduler';
import {
Scheduler,
Appointments,
AppointmentForm,
AppointmentTooltip,
WeekView,
ConfirmationDialog,
} from '#devexpress/dx-react-scheduler-material-ui';
const DataSheet = ( { addShift, shifts, deleteShift } ) => {
const [data, setData] = useState(shifts)
const [currentDate, setCurrentDate] = useState('2018-06-27')
const [addedAppointment, setAddedAppointment] = useState({})
const [appointmentChanges, setAppointmentChanges] = useState({})
const [editingAppointment, setEditingAppointment] = useState(undefined)
const changeAddedAppointment = (addedAppointment) => {
setAddedAppointment({ addedAppointment });
}
const changeAppointmentChanges = (appointmentChanges) => {
setAppointmentChanges({ appointmentChanges });
}
const changeEditingAppointment = (editingAppointment) => {
setEditingAppointment({ editingAppointment });
}
const commitChanges = ({ added, changed, deleted }) => {
setData ((????) => {
let { data } = data;
console.log(data); //returns undefined
if (added) {
const startingAddedId = data > 0 ? data[data.length - 1].id + 1 : 0;
data = [...data, { id: startingAddedId, ...added }];
addShift(added);
}
return { data };
});
}
return (
<Paper>
<Scheduler
data={data}
height={660}
>
<ViewState
currentDate={currentDate}
/>
<EditingState
onCommitChanges={commitChanges}
addedAppointment={addedAppointment}
onAddedAppointmentChange={changeAddedAppointment}
appointmentChanges={appointmentChanges}
onAppointmentChangesChange={changeAppointmentChanges}
editingAppointment={editingAppointment}
onEditingAppointmentChange={changeEditingAppointment}
/>
<WeekView
startDayHour={9}
endDayHour={17}
/>
<Appointments />
<AppointmentTooltip
showOpenButton
showDeleteButton
/>
<AppointmentForm />
</Scheduler>
</Paper>
);
}
export default DataSheet
App.js:
import React from 'react';
import backgroundImage from './Resources/BennyBackground.jpeg'
import Header from "./components/Header";
import { useState, useEffect } from "react"
import DataSheet from './components/DataSheet';
const containerStyle= {
width: '100vw',
height: '100vh',
backgroundImage: `url(${backgroundImage})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
}
const App = () => {
const [shifts, setShifts] = useState([])
useEffect(() => {
const getShifts = async () => {
const shiftsFromServer = await fetchShifts()
setShifts(shiftsFromServer)
}
getShifts()
}, [])
const fetchShifts = async () => {
const res = await fetch(`http://localhost:5000/shifts/`)
const data = await res.json()
return data
}
const addShift = async (shift) => {
const startingAddedId = shifts.length > 0 ? shifts[shifts.length - 1].id + 1 : 0;
shift.id = startingAddedId;
const res = await fetch(`http://localhost:5000/shifts/`,{
method: 'POST',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(shift)
})
const data = await res.json()
setShifts([...shifts, data])
}
return (
<div className="container"
style={containerStyle} >
<div className='secondary_container'>
<Header />
<DataSheet shifts={shifts} addShift={addShift}/>
</div>
</div>
);
}
export default App;
I know it is a lot of code and a lot to ask and I would highly appreciate help with this.
I believe the issue is that you are using setXxx as you would use this.setState. In class components, you have one function that modifies all the state (this.setState), while in function components you have a setter function for each field.
So change this:
const changeAddedAppointment = (addedAppointment) => {
setAddedAppointment({ addedAppointment });
}
to this:
const changeAddedAppointment = (addedAppointment) => {
setAddedAppointment(addedAppointment);
}
As far as the commitChanges function goes, you can do the data manipulation before using setData. Also I'm not sure that this let { data } = data would work since there is already a data field. You can try this:
const commitChanges = ({ added, changed, deleted }) => {
let newData = [...data.data];
if (added) {
const startingAddedId = newData > 0 ? newData [data.length - 1].id + 1 : 0;
newData = [...newData , { id: startingAddedId, ...added }];
addShift(added);
}
setData(newData);
};
i want to show all available usernames when user types # in input field and filtered usernames when user enters anything after # character.
I have implemented like below,
class UserMention extends React.purecomponent {
constructor(props) {
super(props);
this.state = {
text: '',
user_mention: false,
};
this.user='';
}
user_list = [
{name: 'John smith'},
{name: 'Jenna surname2'},
{name: 'Tuija rajala'},
];
get_user = s => s.includes('#') && s.substr(s.lastIndexOf('#') +
1).split(' ')[0];
handle_input_change = (event) => {
let user_mention;
this.user = this.get_user(event.target.value);
if (event.target.value.endsWith('#')) {
user_mention = true;
} else {
user_mention = false;
}
this.setState({
user_mention: user_mention,
[event.target.name]: event.target.value,
});
};
get_text_with_user_mention = (text, selected_user) => {
let user_name = selected_user;
let text_without_user_mention;
text_without_user_mention = text.slice(0,
text.lastIndexOf('#'));
return text_without_user_mention + user_name;
};
handle_select_value = (selected_user) => {
let text;
text = this.get_text_with_user_mention(this.state.text,
selected_user);
this.setState({
text: text,
user_mention: false,
});
this.user = false;
};
render = () => {
let suggested_values = [];
if (this.state.user_mention) {
suggested_values = this.user_list
.map((o) => { return {user_name: o.user_name};});
}
if (this.user) {
suggested_values = this.user_list
.filter(user => user.user_name.indexOf(this.user) !==
-1)
.map((o) => {return {user_name: o.user_name};});
}
return (
<input
required
name="text"
value={this.state.text}
onChange={this.handle_input_change}
type="text"/>
{this.state.user_mention &&
<SelectInput
on_change={this.handle_select_value}
values={suggested_values}/>}
{this.user &&
<SelectInput
on_change={this.handle_select_value}
values={suggested_values}/>}
);
};
}
As you see from above code, i am modifying suggested_values based on this.user and this.state.user_mention state. Can someone help me refactor or modify this a bit more nicer. thanks.
This is another approach using React hooks, instead of classes. If you've never worked with hooks, give it a try. You will enjoy it. It's much simpler in my opinion.
I also added a username property. It's much better if you work with a string that doesn't allow spaces when you're tagging someone. You can also display the full name with spaces along with the username, if you wish.
Ex:
John Smith (#johnsmith)
function App() {
const inputRef = React.useRef(null);
const [inputValue, setInputValue] = React.useState('');
const [userList,setUserList] = React.useState([
{name: 'John smith', username:'johnsmith'},
{name: 'Jenna surname2', username:'jennasurname2'},
{name: 'Tuija rajala', username:'tuijarajala'}
]
);
const [showSuggestions,setShowSuggestions] = React.useState(false);
const [suggestionList,setSuggestionList] = React.useState(
['johnsmith','jennasurname2','tuijarajala']
);
function onChange(event) {
const regexp = /#[a-zA-Z0-9]*$/;
if (regexp.test(event.target.value)) {
setShowSuggestions(true);
}
else {
setShowSuggestions(false);
}
setInputValue(event.target.value);
}
function focusInput() {
inputRef.current.focus();
}
return(
<React.Fragment>
<input ref={inputRef} type='text' value={inputValue} onChange={onChange}/>
{showSuggestions &&
<Suggestions
inputValue={inputValue}
suggestionList={suggestionList}
applyMention={onChange}
focusInput={focusInput}
/>
}
</React.Fragment>
);
}
function Suggestions(props) {
function selectSuggestion(username) {
const regexp = /#[a-zA-Z0-9]*$/;
const newValue = props.inputValue.replace(regexp,username + ' ');
props.applyMention({target: {value: newValue}}); // THIS MIMICS AN ONCHANGE EVENT
props.focusInput();
}
const suggestionItems = props.suggestionList.map((item) =>
<div className="item" onClick={()=>selectSuggestion('#' + item)}>#{item}</div>
);
return(
<div className="container">
{suggestionItems}
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
.container {
border: 1px solid silver;
width: 150px;
}
.item {
cursor: pointer;
}
.item:hover {
color: blue;
}
input {
width: 300px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
You can simplify your code by doing something like this.
See sandbox: https://codesandbox.io/s/react-example-kgm2h
import ReactDOM from "react-dom";
import React from "react";
class UserMention extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
user_list: [
{ name: "John smith" },
{ name: "Jenna surname2" },
{ name: "Tuija rajala" }
],
suggestions: []
};
}
handleOnChange = e => {
const { value } = e.target;
const { user_list } = this.state;
//show all user suggestions
if (value.includes("#") && value.indexOf("#") === value.length - 1) {
this.setState({
text: value,
suggestions: [...this.state.user_list]
});
//show matching user suggesstions
} else if (value.includes("#") && value.length > 1) {
const stringAfterAt = value.slice(value.indexOf("#") + 1).toLowerCase();
const newSuggestions = user_list.filter(user => {
return user.name.toLowerCase().includes(stringAfterAt);
});
this.setState({
text: value,
suggestions: newSuggestions
});
//display no users if they do not use the # symbol
} else {
this.setState({
text: value,
suggestions: []
});
}
};
createSuggestionsList = () => {
const { suggestions } = this.state;
return suggestions.map(user => {
return <div>{user.name}</div>;
});
};
render = () => {
return (
<div>
<input
required
name="text"
value={this.state.text}
onChange={this.handleOnChange}
type="text"
/>
{this.createSuggestionsList()}
{/* <SelectInput value={this.state.suggestions}/> */}
</div>
);
};
}
ReactDOM.render(<UserMention />, document.getElementById("root"));
I'm not entirely sure how you want to render the suggested users, but you can always just pass down this.state.suggestions as a prop to the SelectInput component.
Main takeaway is to use an additional array in our state for suggestions and update it as the user types into the input. We call {this.createSuggestionsList()} inside render to dynamically create the markup for each suggested user. Or as mentioned above, just pass down the suggestions as a prop.
I am working on a simple version of ReactDND before I implement this code into my image uploader.
Each time an image is added, it is added to state and passed through to ReactDND so that it is draggable and also droppable (so users can rearrange their images).
Everything works great, except for one thing. The problem I am having is after adding multiple images, is that once I drag and drop and image (works), the State no longer updates for ReactDND and I cannot add new images.
Here is my code below (note I am just using a button to add extra items to state):
Main Component:
import React from 'react';
// Drag and drop stuff
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Container from './Container';
class ImageUploader extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [],
listCount: 1
};
this.onAddItem = this.onAddItem.bind(this);
}
onAddItem(e) {
e.preventDefault();
var listArray = this.state.list;
var buildObject = {
text: 'Jeremy' + this.state.listCount.toString(),
age: '25',
id: this.state.listCount
};
listArray.push(buildObject);
let newListCount = this.state.listCount + 1;
this.setState({
list: listArray,
listCount: newListCount
});
console.log(this.state.list);
}
render() {
return (
<div>
<h1>Add to List</h1>
<button onClick={this.onAddItem}>Add Item</button>
<h1>The List</h1>
<Container id={1} list={this.state.list} />
</div>
)
}
}
export default DragDropContext(HTML5Backend)(ImageUploader);
Container:
import React, { Component } from 'react';
import update from 'react/lib/update';
import Card from './Card';
import { DropTarget } from 'react-dnd';
class Container extends Component {
constructor(props) {
super(props);
this.state = { cards: props.list };
}
pushCard(card) {
this.setState(update(this.state, {
cards: {
$push: [ card ]
}
}));
}
removeCard(index) {
this.setState(update(this.state, {
cards: {
$splice: [
[index, 1]
]
}
}));
}
moveCard(dragIndex, hoverIndex) {
const { cards } = this.state;
const dragCard = cards[dragIndex];
this.setState(update(this.state, {
cards: {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragCard]
]
}
}));
}
render() {
const { cards } = this.state;
const { canDrop, isOver, connectDropTarget } = this.props;
const isActive = canDrop && isOver;
const style = {
width: "200px",
height: "404px",
border: '1px dashed gray'
};
const backgroundColor = isActive ? 'lightgreen' : '#FFF';
return connectDropTarget(
<div className="houzes-dropbox">
{cards.map((card, i) => {
return (
<Card
key={card.id}
index={i}
listId={this.props.id}
card={card}
removeCard={this.removeCard.bind(this)}
moveCard={this.moveCard.bind(this)} />
);
})}
</div>
);
}
}
const cardTarget = {
drop(props, monitor, component ) {
const { id } = props;
const sourceObj = monitor.getItem();
if ( id !== sourceObj.listId ) component.pushCard(sourceObj.card);
return {
listId: id
};
}
}
export default DropTarget("CARD", cardTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
}))(Container);
Card:
import React, { Component } from 'react';
import { findDOMNode } from 'react-dom';
import { DragSource, DropTarget } from 'react-dnd';
import flow from 'lodash/flow';
const style = {
border: '1px dashed grey',
padding: '0.5rem 1rem',
margin: '.5rem',
backgroundColor: 'white',
cursor: 'move'
};
class Card extends Component {
render() {
const { card, isDragging, connectDragSource, connectDropTarget } = this.props;
const opacity = isDragging ? 0 : 1;
// Background URL
let backgroundUrl = {
backgroundImage: "url(" + "http://localhost:4000/uploads/2017/8/a3ff91dc-2f80-42f7-951a-e9a74bf954d7-1200x800.jpeg" + ")"
};
console.log(card);
return connectDragSource(connectDropTarget(
<div className={`uploadedImageWrapper col-md-6 col-sm-12`}>
<div className="uploadedImage">
<span style={backgroundUrl} />
{card.text}
{card.age}
</div>
</div>
));
}
}
const cardSource = {
beginDrag(props) {
return {
index: props.index,
listId: props.listId,
card: props.card
};
},
endDrag(props, monitor) {
const item = monitor.getItem();
const dropResult = monitor.getDropResult();
if ( dropResult && dropResult.listId !== item.listId ) {
props.removeCard(item.index);
}
}
};
const cardTarget = {
hover(props, monitor, component) {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
const sourceListId = monitor.getItem().listId;
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return;
}
// Determine rectangle on screen
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
// Get vertical middle
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
// Determine mouse position
const clientOffset = monitor.getClientOffset();
// Get pixels to the top
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
// Only perform the move when the mouse has crossed half of the items height
// When dragging downwards, only move when the cursor is below 50%
// When dragging upwards, only move when the cursor is above 50%
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
// Time to actually perform the action
if ( props.listId === sourceListId ) {
props.moveCard(dragIndex, hoverIndex);
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
monitor.getItem().index = hoverIndex;
}
}
};
export default flow(
DropTarget("CARD", cardTarget, connect => ({
connectDropTarget: connect.dropTarget()
})),
DragSource("CARD", cardSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}))
)(Card);
So just to recap, I can add items to state, and they become draggable and droppable. But after having dragged and dropped an element, I can no longer add anymore items to state.
Any ideas as to what the solution would be? What am I doing wrong?
Thank-you for looking through this, and any answers. Cheers.
#Notorious.
I have checked your code in my side and solved the issue.
When you drag and drop an element that changes the state of Container but not the state of ImageUploader.
So I made a function to inform the state of Container has changed.
Also I inserted componentWillReceiveProps() function to Container and updated the state of Container in that function.
Finally the problem solved.
Here's the changed code.
Main Component:
import React from 'react';
// Drag and drop stuff
import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Container from './Container';
class ImageUploader extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [],
listCount: 1
};
this.onAddItem = this
.onAddItem
.bind(this);
this.listChanged = this.listChanged.bind(this);
}
onAddItem(e) {
e.preventDefault();
var listArray = this.state.list;
var buildObject = {
text: 'Jeremy' + this
.state
.listCount
.toString(),
age: '25',
id: this.state.listCount
};
listArray.push(buildObject);
let newListCount = this.state.listCount + 1;
this.setState({list: listArray, listCount: newListCount});
}
listChanged(newList) {
this.setState({
list: newList
})
}
render() {
return (
<div>
<h1>Add to List</h1>
<button onClick={this.onAddItem}>Add Item</button>
<h1>The List</h1>
<Container id={1} list={this.state.list} listChanged={this.listChanged}/>
</div>
)
}
}
export default DragDropContext(HTML5Backend)(ImageUploader);
Container:
import React, { Component } from 'react';
import update from 'react/lib/update';
import Card from './Card';
import { DropTarget } from 'react-dnd';
class Container extends Component {
constructor(props) {
super(props);
this.state = { cards: this.props.list };
}
pushCard(card) {
this.setState(update(this.state, {
cards: {
$push: [ card ]
}
}));
}
removeCard(index) {
this.setState(update(this.state, {
cards: {
$splice: [
[index, 1]
]
}
}));
}
moveCard(dragIndex, hoverIndex) {
const { cards } = this.state;
const dragCard = cards[dragIndex];
this.setState(update(this.state, {
cards: {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragCard]
]
}
}));
}
componentWillReceiveProps(nextProps) {
// You don't have to do this check first, but it can help prevent an unneeded render
if (nextProps.list !== this.state.cards) {
this.props.listChanged(this.state.cards);
}
}
render() {
const { cards } = this.state;
const { canDrop, isOver, connectDropTarget } = this.props;
const isActive = canDrop && isOver;
const style = {
width: "200px",
height: "404px",
border: '1px dashed gray'
};
const backgroundColor = isActive ? 'lightgreen' : '#FFF';
return connectDropTarget(
<div className="houzes-dropbox">
{cards.map((card, i) => {
return (
<Card
key={card.id}
index={i}
listId={this.props.id}
card={card}
removeCard={this.removeCard.bind(this)}
moveCard={this.moveCard.bind(this)} />
);
})}
</div>
);
}
}
const cardTarget = {
drop(props, monitor, component ) {
const { id } = props;
const sourceObj = monitor.getItem();
if ( id !== sourceObj.listId ) component.pushCard(sourceObj.card);
return {
listId: id
};
}
}
export default DropTarget("CARD", cardTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
}))(Container);
I am really happy if this helped you.
Thanks for reading my post.
Vladimir