validation for react-select, getting Cannot read property 'length' of undefined - reactjs

I am new to react and am using react-select for selecting the respective departments. I am trying to validate the select dropdown such that if select dropdown is empty it will give an error message. I am not sure how to do that exactly. It is showing this error:
Departments.continue
D:/react_bpms1/src/components/Departments.js:69
66 | this.setState({ selectedOptions });
67 | };
68 | continue = e => {
> 69 | if (document.getElementById("Departments").value.length < 1) {
| ^ 70 | document.getElementById("departments").style.visibility = "visible";
71 | document.getElementById("Departments").style.border = "1px solid red";
72 | } else {
But here is what I am doing:
const Departments = [
{ label: "OneIT", value: "OneIT" },
{ label: "HR", value: "HR" },
{ label: "Vigilance", value: "Vigilance" },
{ label: "Ethics", value: "Ethics" },
{ label: "Corporate Services", value: "Corporate Services" },
{ label: "Legal", value: "Legal" },
{ label: "Sports", value: "Sports" },
{ label: "TQM", value: "TQM" },
{ label: "Iron Making", value: "Iron Making" },
{ label: "TMH", value: "TMH" }
];
class MultiSelect2 extends Component {
state = {
selectedOptions: []
};
handleChangeField = selectedOptions => {
this.setState({ selectedOptions });
};
render() {
const { selectedOption } = this.state;
return (
<div className="container">
<div className="row">
<div className="col-md-2"></div>
<div className="col-md-8">
<span>Select Department</span>
<Select
id="Departments"
htmlFor="Departments"
value={selectedOption}
options={Departments}
onChange={this.handleChangeField}
isMulti
/>
{this.state.selectedOptions.map(o => (
<p>{o.value}</p>
))}
</div>
<div className="col-md-4"></div>
</div>
</div>
);
}
}
export default MultiSelect2;
Here is where I am calling the Multiselect option:
export class Departments extends Component {
state = {
rows: [],
idx: [],
selectedOptions: []
};
handleChangeField = selectedOptions => {
this.setState({ selectedOptions });
};
continue = e => {
if (document.getElementById("Departments").value.length < 1) {
document.getElementById("departments").style.visibility = "visible";
document.getElementById("Departments").style.border = "1px solid red";
} else {
e.preventDefault();
this.props.nextStep();
}
};
back = e => {
e.preventDefault();
this.props.prevStep();
};
render() {
const { values, handleChange } = this.props;
const { selectedOption } = this.state;
const {
values: {
Title,
Details,
What,
Why,
How,
Status,
Cost,
Benefits,
Kpi_Before,
Kpi_After,
UOM_Before,
UOM_After,
Base_After,
Target_After,
dateTime_After,
Base_Before,
Target_Before,
Time,
dateTime,
departments,
Departments,
selectedOptions
}
} = this.props;
return (
<MuiThemeProvider theme={theme}>
<React.Fragment>
<div className={useStyles.root}>
<AppBar position="static">
<Toolbar>
<Typography
gutterBottom
align="center"
style={{ width: "100%", alignItems: "center" }}
>
Select Departments
</Typography>
</Toolbar>
</AppBar>
</div>
<br />
<br />
<Grid container>
<Grid item xs={6}>
<MultiSelect2
style={{ width: "80%" }}
id="Departments"
onChange={this.handleChangeField}
value={selectedOption}
/>
<label
id="departments"
style={{ visibility: "hidden", color: "red" }}
>
Select Applicable departments
</label>
</Grid>

As #sandeeppradhan you have to stop using these accessors, and think DATA driven ie change the states / props and display them through your interface, dont change styles as you are doing because the dom may change at anytime. And this is a source of bug as the component may not be attached to the dom or a child component may not be ready. Tbh, you are a bit too far in the wrong to make minor update to your code to fix the isssue
To answer more specifically to your question : probably the MultiSelect2 is not rendered when the event occurs.
Bad solution but may interest you : merge the MultiSelect2 inside Departments and use ref + state
ex : 
export class Departments extends Component {
constructor(props) {
super(props);
this.state={
departmentsStyle:{}
};
this.departmentsRef = React.createRef();
}
//...
continue = e => {
if (this.departmentsRef.current && this.departmentsRef.current.value.length < 1) {
this.setState({
departmentsStyle:{
border:"1px solid red",
visibility : "visible"
}
});
} else {
this.setState({
departmentsStyle:{}
});
e.preventDefault();
this.props.nextStep();
}
};
//.... ETC ETC
render() {
//...
return (
///...
//The ref is forwarded to the root element
//(if it does not work use inputRef={departmentsRef})
<Select ref={departmentsRef} style={this.state.departmentsStyle}
//...
)
}
Best solution I think : At this point the only think I would advice is if possible to look at Formik and transfer the FormikBag to the MultiSelect2. You could then manage better the components with the state of the form.
https://jaredpalmer.com/formik/docs/overview
good luck, all the best

Related

I am trying to use Radium in react

So I have two components, Person and App, the person component has some inline CSS styling that I was to use with react, but I am getting the following error.
Module '"../../../../../../Users/7oss/Desktop/ReactTutorials/my-first-portfolio/node_modules/#types/radium"' has no exported member 'StyleRoot'.ts(2305)
I am trying to add #media styling to the Person Component.
I have tried doing Radium.StyleRoot instead of StyleRoot, but I got the following error:
Objects are not valid as a React child (found: object with keys {#media (min-width: 500px)}). If you meant to render a collection of children, use an array instead.
Here is the App Component:
import React, { Component, ChangeEvent } from "react";
import "./App.css";
import Person from "./Components/Person/Person";
import Radium, { StyleRoot } from "radium";
class App extends Component {
state = {
persons: [
{ id: "hoba", name: "Hosam", age: 24 },
{ id: "hoba1", name: "Ayah", age: 18 },
{ id: "hoba2", name: "Test", age: 20 }
],
ShowPersons: false
};
deletePersonHandler = (personIndex: any) => {
// const persons = this.state.persons.slice(); this is one way
const persons = [...this.state.persons]; // Another way
persons.splice(personIndex, 1);
this.setState({ persons: persons });
};
nameChangeHandler = (event: any, id: any) => {
// console.log('Was clicked!!');
// this.state.persons[0].name = "7ossam" DON'T DO THIS!!! USE SETSTATE()
const personIndex = this.state.persons.findIndex(p => {
return p.id === id;
});
const person = { ...this.state.persons[personIndex] };
person.name = event.target.value;
const persons = [...this.state.persons];
persons[personIndex] = person;
this.setState({
persons: persons
});
};
togglePersonsHanddler = () => {
const doesShow = this.state.ShowPersons;
this.setState({ ShowPersons: !doesShow });
};
render() {
const style = {
backgroundColor: "green",
color: "white",
font: "inherit",
border: "1px solid",
cursor: "pointer",
":hover": {
backgroundColor: "lightgreen",
color: "black"
}
};
let persons = null;
if (this.state.ShowPersons) {
persons = (
<div>
{this.state.persons.map((person, index) => {
return (
<Person
name={person.name}
age={person.age}
click={() => this.deletePersonHandler(index)}
key={person.id}
changedName={(event: any) =>
this.nameChangeHandler(event, person.id)
}
/>
);
})}
</div>
);
style.backgroundColor = "red";
style[":hover"] = {
backgroundColor: "salmon",
color: "black"
};
}
let classes = [];
if (this.state.persons.length <= 2) {
classes.push("red");
}
if (this.state.persons.length <= 1) {
classes.push("bold");
}
return (
<StyleRoot>
<div className="App">
<br />
<p className={classes.join(" ")}>Yasta garab el hoba hoba</p>
<button style={style} onClick={this.togglePersonsHanddler}>
Toggle Names
</button>
<br />
<h1>Hello, this is sam!</h1>
{persons}
</div>
</StyleRoot>
);
}
}
export default Radium(App);
And here is the Person Component:
import React, { Component } from "react";
import Radium from "radium";
import "./Person.css";
interface IPersonProps {
name: string;
age: number;
click?: any;
changedName?: any;
}
class Person extends Component<IPersonProps> {
render() {
const style = {
"#media (min-width: 500px)": {
width: "450px"
}
};
return (
<div className="Person">
{" "}
style={style}
<p onClick={this.props.click}>
{" "}
I am {this.props.name} and I am {this.props.age} years old
</p>
<p>{this.props.children}</p>
<input
type="text"
onChange={this.props.changedName}
value={this.props.name}
/>
</div>
);
}
}
export default Radium(Person);
Here is the radium package in package-lock.json:
"#types/radium": {
"version": "0.24.2",
"resolved": "https://registry.npmjs.org/#types/radium/-/radium-0.24.2.tgz",
"integrity": "sha512-AudCpKQH/csx6eB4OZhEdKf8Avm18wX8gLOig5H5iocPDPp3GRUPkQmUOXvsIYO64LyAb4CiIfSmcWUaUdvl4A==",
"requires": {
"#types/react": "*"
}
},
I am assuming that the issue is with StyleRoot isn't imported correctly somehow, but I would appreciate some input. I am also Using TypeScript
I had the same problem. I managed to solve by making the typing of the constant style explicit.
const style: Radium.StyleRules = {
'#media (min-width: 500px)': {
width: '450px'
}
};

React.js: state is not updating in parent component

I have search filter and categories. I just want to have a possibility to reset state in single page application.
Due to React.js I guess I do everything correct to pass state from parent to child and then from child to parent. But, unfortunately, something is going wrong. I tried a lot and what I discovered, that onAddCategory() in DropdownGroup doesn't update current state.
Sorry in advance, I add whole code, my be something there could affect this. But i guess you can see first halfs of two codes and it will be enough.
Thank you in advance.
I have parent component:
class DropdownGroup extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: [], // we have empty array, that pass to CategoryDropdown
};
this.onAddCategory = this.onAddCategory.bind(this);
}
onAddCategory(newCategory) {
this.setState(() => ({
categories: newCategory,
}));
}
onSelectCategory(path) {
this.props.onChangeEvents(path);
}
render() {
const months = ['January', 'February' ... ];
const eventsType = ['Party', 'Karaoke ... ];
const { categories } = this.state;
return (
<ButtonToolbar className="justify-content-center pb-4 pt-4">
{ console.log(categories) }
<CategoryDropdown
items={eventsType}
homePath="events"
path="events/categories/"
categories={categories} // here we pass our empty array (or updated later)
addCategories={this.onAddCategory} // this is what helps to update our array
onApply={(path) => this.onSelectCategory(path)}
/>
<MyDropdown
id="sort-by-month"
name="By month"
items={months}
onSelect={(e) => this.onSelectCategory(`events/month/${e}`)}
/>
<DropdownWithDate
oneDate="events/date/"
rangeDate="events/dates?from="
onApply={(path) => this.onSelectCategory(path)}
/>
<Button
onClick={() => this.setState({ categories: [] })} // here we can reset the value of our array
className="m-button ml-5"
>
Reset
</Button>
</ButtonToolbar>
);
}
}
DropdownGroup.propTypes = {
onChangeEvents: PropTypes.any.isRequired,
};
export default DropdownGroup;
and this is child component
class CategoryDropdown extends Component {
constructor(props) {
super(props);
this.state = {
visible: false,
selected: this.props.categories, // here we get values from props (now empty, then updated values)
};
this.onVisibleChange = this.onVisibleChange.bind(this);
}
onVisibleChange(visible) {
this.setState({
visible: visible,
});
}
saveSelected(selectedKeys) {
this.setState({
selected: selectedKeys,
});
}
addCategories() {
this.props.addCategories(this.state.selected); // here props are updated
}
confirm() {
const { selected } = this.state;
this.addCategories(this.state.selected);
const { homePath, path } = this.props;
if (selected.length > 0) {
this.props.onApply(path + selected);
} else {
this.props.onApply(homePath);
}
this.onVisibleChange(false);
}
render() {
const { visible } = this.state;
const { items } = this.props;
const menu = (
<Menu
multiple
onSelect={(e) => { this.saveSelected(e.selectedKeys); }}
onDeselect={(e) => { this.saveSelected(e.selectedKeys); }}
>
{items.map((item) => (
<MenuItem
key={item.replace('\u0020', '\u005f').toLowerCase()}
>
{item}
</MenuItem>
))}
<Divider />
<MenuItem disabled>
<Container
className="text-center "
style={{
cursor: 'pointer',
pointerEvents: 'visible',
}}
onClick={() => {
this.confirm();
}}
>
Select
</Container>
</MenuItem>
</Menu>
);
return (
<Dropdown
trigger={['click']}
onVisibleChange={this.onVisibleChange}
visible={visible}
closeOnSelect={false}
overlay={menu}
>
<Button className="m-button">By Category</Button>
</Dropdown>
);
}
}
CategoryDropdown.propTypes = {
onApply: PropTypes.any.isRequired,
items: PropTypes.any.isRequired,
path: PropTypes.string.isRequired,
homePath: PropTypes.string.isRequired,
categories: PropTypes.array.isRequired,
addCategories: PropTypes.any.isRequired,
};
export default CategoryDropdown;

How to send props and methods between components?

Hi guys, I'm trying to code little app. I am trainee programmer. I need help to understand how to use props on my app. I'm currently using one component from CodePen and I fetched the data from jsonplaceholder. But I don't know how to pass props between this component and App.js. It was no problem with easier components but here is lot of methods and events. With this tagged input I want to add or delete items.
import React from "react";
import StateFullComponent from "./components/StateFullComponent";
import StatelessComponent from "./components/StatelessComponent";
import TagInput from "./components/TagInput";
function App() {
return (
<div>
<StatelessComponent
props1={"String"}
props2={1}
props3={true}
props4={JSON.stringify({ value: "value", key: 1 })}
/>
<StateFullComponent items={["apple", "orrange", "pear", "male"]} />
<TagInput />
</div>
);
}
export default App;
import React, { Component } from "react";
export default class TagInput extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
focused: false,
input: ""
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleInputKeyDown = this.handleInputKeyDown.bind(this);
this.handleRemoveItem = this.handleRemoveItem.bind(this);
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
return response.json();
})
.then(result => {
this.setState({
users: result
});
});
}
add() {
let value = Math.floor(Math.random() * 10 + 1);
let users = this.state.users;
users.push(value);
this.setState({ users: users });
}
handleInputChange(evt) {
this.setState({ input: evt.target.value });
}
handleInputKeyDown(evt) {
if (evt.keyCode === 13) {
const { value } = evt.target;
this.setState(state => ({
users: [...state.users, value],
input: ""
}));
}
if (
this.state.users.length &&
evt.keyCode === 8 &&
!this.state.input.length
) {
this.setState(state => ({
users: state.users.slice(0, state.users.length - 1)
}));
}
}
handleRemoveItem(index) {
return () => {
this.setState(state => ({
users: state.users.filter((user, i) => i !== index)
}));
};
}
render() {
console.log(this.props, ":::::::::::");
const { users } = this.state;
const userId = users.map((user, id) => <li key={id}>{user.name}</li>);
const styles = {
container: {
border: "1px solid #ddd",
padding: "5px",
borderRadius: "5px"
},
items: {
display: "inline-block",
padding: "2px",
border: "1px solid blue",
fontFamily: "Helvetica, sans-serif",
borderRadius: "5px",
marginRight: "5px",
cursor: "pointer"
},
input: {
outline: "none",
border: "none",
fontSize: "14px",
fontFamily: "Helvetica, sans-serif"
}
};
return (
/* <div>
<ul>{userId}</ul>
<button onClick={this.handleRemoveItem().bind(this)}>add</button>
</div> */
<label>
<ul style={styles.container}>
{this.state.users.map((user, i) => (
<li
key={i}
style={styles.users}
onClick={this.handleRemoveItem(i).bind(this)}
>
{user}
<span>(x)</span>
</li>
))}
<input
style={styles.input}
value={this.state.input}
onChange={this.handleInputChange.bind(this)}
onKeyDown={this.handleInputKeyDown.bind(this)}
/>
</ul>
</label>
);
}
}
In your componentDidMount you are fetching data, and getting back an array of objects, and setting the state value users to the array of objects. That's all good and exactly what you should be doing.
The problem is in the render method when you are looping through the array of users. Remember that each user in the array is an object. Look at the jsx you have within the li element. You are rendering the user object, and an object is an invalid react child. Instead, you need to render the particular fields from the object.
Example, if the object contains a name field, and an email field, render {user.name} or {user.email}. That way you can render the particular fields of data from the user object.
<li
key={i}
style={styles.users}
onClick={this.handleRemoveItem(i).bind(this)}
>
Name: {user.name}
Email: {user.email}
Id: {user.id}
<span>(x)</span>
</li>
It seems you may still have some questions about passing props to a component. This only addresses the particular error you are seeing. If you still have questions let me know.

Reactjs displays users info serially going up irrespective of the user clicked

Reactjs displays users info serially going up irrespective of the user clicked.
I have 5 users in the array.
The code below was meant to display each Person Id and Name uniquely on their various popup box when their corresponding name
in the list button is clicked.
Here is my problem:
My issue is that if I click for instance on user 1, instead of getting content of user 1 displayed on its own popup box
it will display content of user 5.
If I pick another user randomly for example user 3 from the list, instead of getting the content of user 3 displayed on its
own popup box, it will display content of user 4 and next click of any user will display content of user 3 and so on going up to user 1.
Is this problem caused from person.id alignment or from serial alignment of users info in the array list?. can someone help me out
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import axios from 'axios';
class App extends Component {
constructor() {
super();
this.state = {
showBox: false,
data: [
{ id: "1", name: "user 1" },
{ id: "2", name: "user 2"},
{ id: "3", name: "user 3"},
{ id: "4", name: "user 4"},
{ id: "5", name: "user 5"},
],
};
this.showBox = this.showBox.bind(this);
this.closeBox = this.closeBox.bind(this);
}
showBox = (pid, name) => {
this.setState({ person_id: pid });
const dataSet = this.state.data;
alert(dataSet);
if ($.inArray(pid, dataSet) != -1)
{
dataSet.splice($.inArray(pid, this.state.data), 1);
}
dataSet.unshift(pid);
var s = 270 ; // start position
var j = 260; //next position
$.each(dataSet, function( index, value ) {
if(index < 4){
$('[rel="'+value+'"]').css("right",s);
$('[rel="'+value+'"]').show();
s = s+j;
}
else{
$('[rel="'+value+'"]').hide();
}
});
//event.preventDefault();
//event.preventDefault();
this.setState({ showBox: true }, () => {
document.addEventListener('click', this.closeBox);
});
}
closeBox(event) {
if (this.cBox.contains(event.target)) {
this.setState({ showBox: false }, () => {
document.removeEventListener('click', this.closeBox);
});
}
}
render() {
return (
<div >
<ul style={{float: "right"}}>
{this.state.data.map((person) => (
<div className="chat-sidebar" key={person.id}>
<button onClick={ () => this.showBox(person.id, person.name)}>
{person.name} </button>
{this.state.showBox
? (
<div rel={person.id} className="msg_box" style={{right: '270px',position: 'fixed', bottom:'-5px', width:'250px',background: 'white',borderRadius:'5px 5px 0px 0px', height: '200px'}}>
<div> <div style={{background: 'red',height: '150px'}}>
<div ref={(element) => {this.cBox = element;}} style={{color: 'blue'}}>Close</div>
Each Users info will appear here below.<br />
(person Id: {person.id})<br />
(person Name: {person.name})<br />
</div>
</div>
</div>
): (
null
)}
</div>
))}
</ul>
</div>
);
}
}
Screenshot updates
Updated Code as Requested by Sir Win
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import axios from 'axios';
class User extends React.Component {
open = () => this.props.open(this.props.data.id, this.props.data.name);
render() {
return (
<React.Fragment>
<div key={this.props.data.id}>
<button onClick={() => this.open(this.props.data.id,this.props.data.name)}>{this.props.data.name}</button>
</div>
</React.Fragment>
);
}
}
class OpenedUser extends React.Component {
close = () => this.props.close(this.props.data.id);
render() {
return (
<div style={{ display: 'inline-block' }}>
<div onClick={this.toggle} className="msg_head">
(<b style={{ color: 'orange' }}>
Minimize
</b>)
<button onClick={this.close}>close</button>
<div>user {this.props.data.id}</div>
<div>name {this.props.data.name}</div>
<div className="msg_wrap"><div className="msg_body">Message will appear here</div></div>
</div>
</div>
)
}
}
class ChatReact extends React.Component {
constructor() {
super();
this.state = {
shownToggle: true,
activeIds: [],
data: [
{ id: 1, name: "user 1" },
{ id: 2, name: "user 2" },
{ id: 3, name: "user 3" },
{ id: 4, name: "user 4" },
{ id: 5, name: "user 5" }
]
};
}
open = (id,name) => {
alert(name);
alert(id);
this.setState((prevState) => ({
activeIds: prevState.activeIds.find((user) => user === id)
? prevState.activeIds
: [...prevState.activeIds, id]
}));
}
close = id => {
this.setState((prevState) => ({
activeIds: prevState.activeIds.filter((user) => user !== id),
}));
};
renderUser = (id) => {
const user = this.state.data.find((user) => user.id === id);
if (!user) {
return null;
}
return (
<OpenedUser data={user} close={this.close}/>
)
}
renderActiveUser = () => {
return (
<div style={{ position: "fixed", bottom: 0, right: 0 }}>
{this.state.activeIds.map((id) => this.renderUser(id)) }
</div>
);
};
render() {
var hidden = {
display: this.state.shownToggle ? "block" : "none"
}
return (
<div>
{this.state.data.map(person => (
<User key={person.id} data={person} open={this.open} />
))}
{this.state.activeIds.length !== 0 && this.renderActiveUser()}
</div>
);
}
}
Welcome to StackOverflow. To get around this problem, I would use .find instead of $.inArray so that we can filter down the data into one result so that we can use it to display the information in the info box.
Avoid using document.addEventListener and document.removeEventListener and rely purely on state. If you're trying to achieve something not listed in the question, then please explain.
Also, try and avoid using jQuery with React.
Here's an example:
class User extends React.Component {
open = () => this.props.open(this.props.data.id);
render() {
return (
<React.Fragment>
<div key={this.props.data.id}>
<button onClick={this.open}>{this.props.data.name}</button>
</div>
</React.Fragment>
);
}
}
class OpenedUser extends React.Component {
close = () => this.props.close(this.props.data.id);
render() {
return (
<div style={{ display: 'inline-block' }}>
<button onClick={this.close}>close</button>
<div>user {this.props.data.id}</div>
<div>name {this.props.data.name}</div>
</div>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
activeIds: [],
data: [
{ id: 1, name: "user 1" },
{ id: 2, name: "user 2" },
{ id: 3, name: "user 3" },
{ id: 4, name: "user 4" },
{ id: 5, name: "user 5" }
]
};
}
open = id => {
this.setState((prevState) => ({
activeIds: prevState.activeIds.find((user) => user === id)
? prevState.activeIds
: [...prevState.activeIds, id]
}));
}
close = id => {
this.setState((prevState) => ({
activeIds: prevState.activeIds.filter((user) => user !== id),
}));
};
renderUser = (id) => {
const user = this.state.data.find((user) => user.id === id);
if (!user) {
return null;
}
return (
<OpenedUser data={user} close={this.close}/>
)
}
renderActiveUser = () => {
return (
<div style={{ position: "fixed", bottom: 0, right: 0 }}>
{this.state.activeIds.map((id) => this.renderUser(id)) }
</div>
);
};
render() {
return (
<div>
{this.state.data.map(person => (
<User key={person.id} data={person} open={this.open} />
))}
{this.state.activeIds.length !== 0 && this.renderActiveUser()}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Changes to react component not showing in view - one character change shows up in onChange

Problem:
I can't seem to change the placeholder in my input tags (inside <Details />).
Expected Behaviour:
That the input tags will be updated using the component's state and its handleChange function.
Actual Behaviour: (sorry for the mess, it's a work in progress!)
The changes made to the input fields only register one character.
Example: Selected all characters in FileName and deleting them shows them as deleted in the console.log() in the handleChange function. However, the field is never changed on the view, but holds the same value.
What I've tried:
I've tried using props and state to handle the changes, but nothing works. I'm guessing I might have to hoist the change up to the ancestor like I've done with the FileItem, but I'm not sure.
Actual Code:
import * as React from "react";
import { IFileModel, IFileWrapper } from "../models/fileModel";
import { Fortress } from "../../dependencies/fortress";
let divStyle = {
width: "50px",
border: "1px solid #000",
padding: "5px",
margin: "5px"
};
declare let config : {[key : string] : string};
const $F = new Fortress(config);
export class DetailsPanel extends React.Component<IFileModel, any> {
constructor(props : IFileModel) {
super(props);
this.state = {
isDisplayed : false
};
this.toggle = this.toggle.bind(this);
}
toggle() {
let newIsCollapsed = !(this.state.isDisplayed);
this.setState({ isDisplayed : newIsCollapsed })
}
render() {
return(
<div>
<div>
<input className="button" type="submit" value={ (!this.state.isDisplayed) ? "Show Panel" : "Hide Panel" } onClick={this.toggle} />
</div>
{ this.state.isDisplayed ? <Details {...this.props} /> : null }
</div>
);
}
}
export class Details extends React.Component <IFileModel, any> {
constructor(props : IFileModel) {
super(props);
this.state = {
currentFile : this.props
};
this.onSubmit = this.onSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
// http://localhost/admin/shared/file/SaveFile/?
/*
FileId: 1
FileName: shirt.pdf
LCID: 2057
FileVariation: 0
DisplayName: shirt.png
OriginalFileName: shirt__1_3_2057.pdf
CategoryId: 0
FileType: 1
FileExtension: png
FileSize: 419920
Width: 615
Height: 462
FileData:
AllowedVariantTypes: 0
RequireAuthorization: 0
AdminId: 1
CreationDate: /Date(1450692426023)/
ModifyDate: /Date(1450692426023)/
ExpiryDate: /Date(253402300799997)/
*/
handleChange(e : any) {
console.log(e.target.name, e.target.value);
this.setState({ [e.target.name] : e.target.value });
}
onSubmit() {
const state = this.state.currentFile;
console.log($F.writeAdminUrl("shared", "file", "SaveFile"));
console.log(JSON.stringify(state));
// fetch($F.writeAdminUrl("shared", "file", "SaveFile"), {
// method: "POST",
// headers: {
// "credentials": "same-origin",
// "mode": "no-cors",
// "Content-Type" : "application/json"
// },
// body : JSON.stringify({ fileId : state.fileId })
// })
}
render() {
const currentFile = this.props;
return(
<div style={{border: "1px dashed black", padding: "4px"}}>
<div>
<p> File Name: </p>
<input type="text" value={currentFile.fileName} name="fileName" onChange={this.handleChange} />
</div>
<div>
<p> Image: </p>
<input type="text" value={currentFile.imageUrl} name="imageUrl" onChange={this.handleChange} />
</div>
<div>
<p> Creation date: </p>
<input type="text" value={currentFile.creationDate} name="creationDate" onChange={this.handleChange} />
</div>
<div>
<p> LCID: </p>
<input type="text" name="LCID" value={currentFile.LCID} onChange={this.handleChange} />
</div>
<div>
<input onClick={(currentFile) ? this.onSubmit : null} type="submit"/>
</div>
</div>
);
}
}
export class FileItem extends React.Component<IFileModel> {
constructor(props : IFileModel) {
super(props);
this.onSelect = this.onSelect.bind(this);
}
onSelect() {
this.props.onSelect(this.props);
}
render() {
return (
<div className="fileItem" style={{ width: "100px", height: '150px', float: 'left' }} onClick={this.onSelect}>
<img src={"http://localhost/content/image/" + this.props.imageUrl} style={divStyle} />
{this.props.fileName}
<button className="edit" />
</div>
);
}
}
export class FileList extends React.Component<any, any> {
constructor(props : any) {
super(props);
this.state = { files: [], skip: 0, take: 10 }
this.increaseTake = this.increaseTake.bind(this);
this.onSelect = this.onSelect.bind(this);
}
getImages(skip : number, take : number, shouldAdd : boolean) {
var that = this;
fetch("http://localhost/admin/shared/file/GetImages?take=" + take + "&skip=" + skip + "&FileType=0&_2331223a3543as", {
credentials: "same-origin", mode: "no-cors"
})
.then(function(response) {
return response.json();
})
.then(function (results) {
var newFiles = results.data.map((file : any) => ({
imageUrl: file.OriginalFileName,
fileName: file.DisplayName,
fileId: file.FileId,
creationDate: file.CreationDate,
LCID: file.LCID
}));
if (shouldAdd) {
newFiles = that.state.files.concat(newFiles);
}
that.setState({ files: newFiles });
});
}
onSelect(file : IFileModel) {
this.props.onFileSelected(file);
}
increaseTake() {
var currentSkip = this.state.skip + 10;
this.setState({ skip: currentSkip });
this.getImages(currentSkip, this.state.take, true);
}
componentWillMount() {
// perfom ajax call
this.getImages(0, this.state.take, true);
}
render() {
return (<div>
{this.state.files.map((item: IFileModel, index: number) =>
<FileItem key={index} {...item} onSelect={this.onSelect} />
)
}
<div style={{ clear: 'both' }}></div>
<div onClick={this.increaseTake}>take more</div>
</div>)
}
}
export class FileGallery extends React.Component <any, any> {
constructor(props : any) {
super(props);
let fm : IFileModel;
this.state = {
sidebarCollapsed : false,
selectedFile : fm
};
this.onFileSelected = this.onFileSelected.bind(this);
}
onFileSelected(file : IFileModel) {
this.setState({ selectedFile : file });
}
render() {
const selectedFile = this.state.selectedFile;
return (
<div>
<div className="FileGalleryHeader">
<div>Galley</div>
<div>Upload</div>
</div>
<div className="FileGalleyMain" style={{ width: "80%", display : 'block', height:"400px", overflow : "auto", float : "left"}}>
<FileList onFileSelected={this.onFileSelected}/>
</div>
<div style={{ width: "20%", display: "block", float : "right", height: "800px"}}>
<DetailsPanel {...selectedFile} onSubmit={(selectedFile) ? selectedFile.onSubmit : null } />
</div>
</div>);
}
}
Model:
export interface IFileModel {
fileId: number;
fileName: string;
categoryId: number;
fileType: number;
fileExtension: string;
fileSize: string;
width: number;
height: number;
creationDate: string;
imageUrl: string;
LCID: string;
onSubmit: Function;
onSelect: Function;
}
export interface IFileWrapper {
file: IFileModel;
}
As far as the code I viewed, it may has two issues:
1. You are setting a wrong state in Details
handleChange(e : any) {
console.log(e.target.name, e.target.value);
this.setState({ [e.target.name] : e.target.value });
}
this should be:
handleChange(e : any) {
console.log(e.target.name, e.target.value);
this.setState({ currentFile: {
...this.state.currentFile,
[e.target.name]: e.target.value
});
}
2. You should not set any props as the input value, it should be state instead
const currentFile = this.props;
return(
<div style={{border: "1px dashed black", padding: "4px"}}>
<div>
<p> File Name: </p>
<input type="text" value={currentFile.fileName} name="fileName" onChange={this.handleChange} />
</div>
this should be:
const currentFile = this.state;
return(
<div style={{border: "1px dashed black", padding: "4px"}}>
<div>
<p> File Name: </p>
<input type="text" value={currentFile.fileName} name="fileName" onChange={this.handleChange} />
</div>

Resources