I'm studying react.js.
How to correctly add class 'use' to the element where the click occurs? From other elements it needs to be removed.
How to get rid of the index, but be able to handle and dispose of the items?
var DB = [
{
name: 'Имя 1', url: 'http://localhost:1', use: true
},
{
name: 'Имя 2', url: 'http://localhost:2', use: false
},
{
name: 'Имя 3', url: 'http://localhost:3', use: false
}
];
class SideBarEl extends React.Component {
hoverLi(t){
if(t.target.id !== ''){
for (var i = 0; i < DB.length; i++){
if(t.target.id == i){
DB[i].use = true;
} else {
DB[i].use = false;
}
}
}
}
render(){
var newsTemplate = DB.map(function(item, index) {
return (
<li key={ index } id={ index } onClick={ this.hoverLi.bind(this)} className={ item.use ? 'use' : '' }>
{ item.name }
<span>
{ item.url }
</span>
</li>
)
}, this);
return(
<ul>{newsTemplate}</ul>
)
}
}
1 Set this.state
You need to use React state to handle such things and rerender when action occurs. If you just use a variable, React doesn't know when something should be rerendered.
this.state = {
links: [
{
name: "Имя 1",
url: "http://localhost:1",
use: true
},
{
name: "Имя 2",
url: "http://localhost:2",
use: false
},
{
name: "Имя 3",
url: "http://localhost:3",
use: false
}
]
};
Read more about state on https://facebook.github.io/react/docs/state-and-lifecycle.html
2 Update state by using onClick
handleClick(item) {
this.setState(prevState => ({
links: prevState.links.map(link => {
link.use = link === item;
return link;
})
}));
}
render() {
// rest of code...
<li
key={item.url}
id={index}
onClick={() => this.handleClick(item)}
className={item.use ? "use" : ""}
>
// rest of code...
}
For only 3 links it's okay to have such non-optimized code. If you would like to apply this for big collection of links (hundreds or thousands of links), it will require a bit more work but probably it's out of you question's scope.
3 Demo
https://codesandbox.io/s/mQoomVOmA
If you click on a link, it will be red and rest will be black because I added this small CSS .use { color: red; }
Have fun and happy coding.
Related
I try to change state of selected chart ( others who are not selected to use default state ), here is the list of charts in ChartWidget component which is child of SmartTradePageLayout:
const charts = [
{
title: 'Chart-1',
id: 0,
content: options,
isSelected: false,
},
{
title: 'Chart-2',
id: 1,
content: options2,
isSelected: false,
},
{
title: 'Chart-3',
id: 2,
content: options3,
isSelected: false,
},
{
title: 'Chart-4',
id: 3,
content: options4,
isSelected: false,
},
]
In this part of code I use map and with handle data function I change color of selected chart but state of others is also changed.
const handleData = (getData) => {
actions[uiActions.actionTypes.MULTICHART_UPDATES]({data: getData});
actions[uiActions.actionTypes.MULTICHART_INVOKED](getData);
setActive(getData.id);
}
charts.map((data, i) => {
return <div key={i} onClick={() => handleData(data)} className={active == i ?
'bg-primary border-inset rounded-top' : 'bg-secondary border-inset roundedtop'}>
<div className='p-1 cursor-pointer' > {data.title} </div>
<TradingViewWidget { ...data.content } />
</div>
}
In this action file for updating state, I use handleClick function and get data for selected chart but state is updated in all charts.
Here is also part of model where I use empty data object to catch data of selected chart.
const handleClick = ( actions, payload ) => {
const { data } = payload;
const { updateState } = actions;
if (data.isSelected == false){
updateState(data.isSelected = true);
console.log('data:', payload);
}
}
Model:
data: { },
Here is SmartTradePageLayout parent component:
const isContentAvailable = !isSingle ?
Boolean(!isEmpty(packsState.selectedPair) && !isEmpty(exchanges) &&
!isEmpty(packsState.exchangeData)) :
Boolean(!isEmpty(exchanges) && singlePair.mainCoin && singlePair.pairCoin );
!isContentAvailable ?
<div className="smart-trade-chart-container">
<ChartWidget defaultPair={DEFAULT_PAIR} />
</div> :
<div className="smart-trade-chart-container">
<ChartWidget/>
</div>
I have a class component that Renders a list of elements and I need to focus them when an event occurs.
Here is an example code
class page extends React.Component {
state = {
items: [array of objects]
}
renderList = () => {
return this.state.items.map(i => <button>{i.somekey}</button>)
}
focusElement = (someitem) => {
//Focus some item rendered by renderList()
}
render(){
return(
<div>
{this.renderList()}
<button onClick={() => focusElement(thatElement)}>
</div>
)
}
}
I know that I need to use refs but I tried several ways to do that and I couldn't set those refs properly.
Can someone help me?
you should use the createRefmethod of each button that you would like to focus, also you have to pass this ref to the focusElement method that you have created:
const myList = [
{ id: 0, label: "label0" },
{ id: 1, label: "label1" },
{ id: 2, label: "label2" },
{ id: 3, label: "label3" },
{ id: 4, label: "label4" },
{ id: 5, label: "label5" }
];
export default class App extends React.Component {
state = {
items: myList,
//This is the list of refs that will help you pick any item that ou want to focus
myButtonsRef: myList.map(i => React.createRef(i.label))
};
// Here you create a ref for each button
renderList = () => {
return this.state.items.map(i => (
<button key={i.id} ref={this.state.myButtonsRef[i.id]}>
{i.label}
</button>
));
};
//Here you pass the ref as an argument and just focus it
focusElement = item => {
item.current.focus();
};
render() {
return (
<div>
{this.renderList()}
<button
onClick={() => {
//Here you are able to focus any item that you want based on the ref in the state
this.focusElement(this.state.myButtonsRef[0]);
}}
>
Focus the item 0
</button>
</div>
);
}
}
Here is a sandbox if you want to play with the code
I am trying to use JsonSchema-Form component but i ran into a problem while trying to create a form that, after choosing one of the options in the first dropdown a secondary dropdown should appear and give him the user a different set o options to choose depending on what he chose in the first dropdown trough an API call.
The thing is, after reading the documentation and some examples found here and here respectively i still don't know exactly how reference whatever i chose in the first option to affect the second dropdown. Here is an example of what i have right now:
Jsons information that are supposed to be shown in the first and second dropdowns trough api calls:
Groups: [
{id: 1,
name: Group1}
{id: 2,
name: Group2}
]
User: [User1.1,User1.2,User2.1,User2.2,User3.1,User3.2, ....]
If the user selects group one then i must use the following api call to get the user types, which gets me the the USER json.
Component That calls JSonChemaForm
render(){
return(
<JsonSchemaForm
schema={someSchema(GroupOptions)}
formData={this.state.formData}
onChange={{}}
uiSchema={someUiSchema()}
onError={() => {}}
showErrorList={false}
noHtml5Validate
liveValidate
>
)
}
SchemaFile content:
export const someSchema = GroupOptions => ({
type: 'object',
required: [
'groups', 'users',
],
properties: {
groups: {
title: 'Group',
enum: GroupOptions.map(i=> i.id),
enumNames: GroupOptions.map(n => n.name),
},
users: {
title: 'Type',
enum: [],
enumNames: [],
},
},
});
export const someUISchema = () => ({
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
},
types: {
'ui:options': {
size: {
lg: 15,
},
},
},
});
I am not really sure how to proceed with this and hwo to use the Onchange method to do what i want.
I find a solution for your problem.There is a similar demo that can solve it in react-jsonschema-form-layout.
1. define the LayoutField,this is part of the demo in react-jsonschema-form-layout.To make it easier for you,I post the code here.
Create the layoutField.js.:
import React from 'react'
import ObjectField from 'react-jsonschema-form/lib/components/fields/ObjectField'
import { retrieveSchema } from 'react-jsonschema-form/lib/utils'
import { Col } from 'react-bootstrap'
export default class GridField extends ObjectField {
state = { firstName: 'hasldf' }
render() {
const {
uiSchema,
errorSchema,
idSchema,
required,
disabled,
readonly,
onBlur,
formData
} = this.props
const { definitions, fields, formContext } = this.props.registry
const { SchemaField, TitleField, DescriptionField } = fields
const schema = retrieveSchema(this.props.schema, definitions)
const title = (schema.title === undefined) ? '' : schema.title
const layout = uiSchema['ui:layout']
return (
<fieldset>
{title ? <TitleField
id={`${idSchema.$id}__title`}
title={title}
required={required}
formContext={formContext}/> : null}
{schema.description ?
<DescriptionField
id={`${idSchema.$id}__description`}
description={schema.description}
formContext={formContext}/> : null}
{
layout.map((row, index) => {
return (
<div className="row" key={index}>
{
Object.keys(row).map((name, index) => {
const { doShow, ...rowProps } = row[name]
let style = {}
if (doShow && !doShow({ formData })) {
style = { display: 'none' }
}
if (schema.properties[name]) {
return (
<Col {...rowProps} key={index} style={style}>
<SchemaField
name={name}
required={this.isRequired(name)}
schema={schema.properties[name]}
uiSchema={uiSchema[name]}
errorSchema={errorSchema[name]}
idSchema={idSchema[name]}
formData={formData[name]}
onChange={this.onPropertyChange(name)}
onBlur={onBlur}
registry={this.props.registry}
disabled={disabled}
readonly={readonly}/>
</Col>
)
} else {
const { render, ...rowProps } = row[name]
let UIComponent = () => null
if (render) {
UIComponent = render
}
return (
<Col {...rowProps} key={index} style={style}>
<UIComponent
name={name}
formData={formData}
errorSchema={errorSchema}
uiSchema={uiSchema}
schema={schema}
registry={this.props.registry}
/>
</Col>
)
}
})
}
</div>
)
})
}</fieldset>
)
}
}
in the file, you can define doShow property to define whether to show another component.
Next.Define the isFilled function in JsonChemaForm
const isFilled = (fieldName) => ({ formData }) => (formData[fieldName] && formData[fieldName].length) ? true : false
Third,after you choose the first dropdown ,the second dropdown will show up
import LayoutField from './layoutField.js'
const fields={
layout: LayoutField
}
const uiSchema={
"ui:field": 'layout',
'ui:layout': [
{
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
}
},
{
users: {
'ui:options': {
size: {
lg: 15,
},
},
doShow: isFilled('groups')
}
}
]
}
...
render() {
return (
<div>
<Form
schema={schema}
uiSchema={uiSchema}
fields={fields}
/>
</div>
)
}
I have an array of objects like this
const Guide = [
{
id: 1,
title: 'Dashboard',
content: 'The dashboard is your main homepage. It will display a feed of looks...'
},
{
id: 2,
title: 'Discover',
content: 'Discover allows you to find new looks, what is trending and search for looks, brands and users'
},
{
id: 3,
title: "Upload you look, style guide and more "
},
{
id: 4,
title: "Upload you look, style guide and more "
},
{
id: 5,
title: "Upload you look, style guide and more "
}
]
I want to be able to click a button and go to the display the data of the next object up to the last one. Currently when I click the button it just changes to the second object "Discover" and stops there, how can I ensure that it goes through with this functionality. Please excuse my grammar.
This is my state when the component mounts
componentWillMount(){
this.setState({
index: Guide[0]
})
}
The initial state index is = 0, And this is my function to go to the next object
moveNext = () => {
let i = Guide.indexOf(Guide[0])
if(i >= 0 && i < Guide.length)
this.setState({
index: Guide[i + 1]
})
}
Change this
moveNext = () => {
let i = Guide.indexOf(Guide[0])
if(i >= 0 && i < Guide.length)
this.setState({
index: Guide[i + 1]
})
}
To this
moveNext = () => {
let i = Guide.indexOf(this.state.index)
if(i >= 0 && i < Guide.length)
this.setState({
index: Guide[i + 1]
})
}
Explanation:
This let i = Guide.indexOf(Guide[0]) makes you keep setting the i value to 0, thats why when you click next you keep getting the second data.
By change it to this let i = Guide.indexOf(this.state.index) you will set the i value to the current index, not 0.
I hope my explanation is understandable :)
Your state should contain the minimal information required, which in this case is the index of the item in the array. You could also use the id, but that would require extra work and it looks like they map directly onto the indices anyway.
const info = [{
title: 'Dashboard',
content: 'The dashboard is your main homepage. It will display a feed of looks...'
},
{
title: 'Discover',
content: 'Discover allows you to find new looks, what is trending and search for looks, brands and users'
},
{
title: "Upload you look, style guide and more "
}
];
class Guide extends React.Component {
constructor() {
super();
this.state = {
index: 0
};
}
goToNext = () => {
this.setState({ index: (this.state.index + 1) % info.length });
};
render() {
const item = info[this.state.index];
return (<div>
<h2>{item.title}</h2>
<p>{item.content}</p>
<button onClick={this.goToNext}>next</button>
</div>);
}
}
ReactDOM.render(<Guide/>, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
I'm not sure what you're trying to do with "let i = Guide.indexOf(Guide[0])".
Try incrementing the index of the state by 1, like this:
componentWillMount() {
this.state = { index: 0 };
}
moveNext = () => {
let i = this.state.index;
if(i >= 0 && i < Guide.length) {
this.setState({
index: i + 1
});
}
}
In the render method, use Guide[this.state.index] to get the data of the current index.
I'm looking to be able to open/close a nested list using React, so that when you click on the parent li the children are hidden? Here's what I used to create the list.
list.js
class List extends React.Component {
render(){
return (
<ul>
{this.props.list.map(function(item){
return (
<li>
<ListItem key={item.id} item={item} />
<List list={item.children} />
</li>
);
})}
</ul>
);
}
list-item.js
class ListItem extends React.Component {
handleCollapse(){
console.log('Open/Close: ' + this.props.item.display_name);
return false;
}
handleFilter(){
console.log('Filter id: ' + this.props.item.id);
return false;
}
render(){
return (
<div>
<a rel="{this.props.item.id}" onClick={this.handleCollapse.bind(this)}>
{this.props.item.display_name}
</a>
<input value="" type="checkbox" onClick={this.handleFilter.bind(this)} />
</div>
)
}
Are you trying to simulate accordion behaviour? If yes then you can modify your code like this. Use component's state and toggle it to open and close children. Instead of creating <List list={item.children} /> in List class, import(or use require) list.js in list-item.js and render the child List item on the basis of current ListItem's state.
list-item.js
class ListItem extends React.Component {
//Add this
constructor (){
super(...arguments);
this.state = { showChild:false};
}
handleCollapse(){
console.log('Open/Close: ' + this.props.item.display_name);
//Add this
this.setState({showChild:!this.state.showChild});
return false;
}
handleFilter(){
console.log('Filter id: ' + this.props.item.id);
return false;
}
render(){
let children;
if(this.state.showChild) {
children = (<List list={this.props.item.children} />);
}
return (
<div>
<a rel="{this.props.item.id}" onClick={this.handleCollapse.bind(this)}>
{this.props.item.display_name}
</a>
<input value="" type="checkbox" onClick={this.handleFilter.bind(this)} />
//Add this
{children}
</div>
)
};
}
list.js
class List extends React.Component {
render(){
//Removed <List .../>, rest is same just other way of doing same stuff
let LI = this.props.list.map( (item) => {
return( <li> <ListItem key={item.id} item={item} /></li>);
}
);
return ( <ul>{LI}</ul>);
}
};
dummy data to test
var data=[
{
id:"1st of Level 1",
get display_name(){return _name(this.id,this.children)},
children: [
{
id:"1st of Level 1.2",
get display_name(){return _name(this.id,this.children)},
children: [
{
id:"1st of Level 1.2.1",
get display_name(){return _name(this.id,this.children)},
children:[]
}
]
},
{
id:"2nd of Level 1.2",
get display_name(){return _name(this.id,this.children)},
children:[]
}
]
},
{
id:"2nd of Level 1",
get display_name(){return _name(this.id,this.children)},
children:[]
},
{
id:"3rd of Level 1",
get display_name(){return _name(this.id,this.children)},
children:[]
},
{
id:"4th of Level1",
get display_name(){return _name(this.id,this.children)},
children:[]
}
];
function _name(id,child) {
if(child.length>0)
return("I am "+id+" I HAVE CHILDREN");
else {
return("I am "+id+" I DON'T HAVE CHILDREN ");
}
}
ReactDOM.render(<List list={data} />,document.getElementById('root'));
`
Doing this recursively is a good way to go about this. What you can do is to give each list item element an onClick handler.
<li onClick={this._toggleChildrenInit.bind(this, item.id)}>
I don't know much about what you're id's are like. But consider the following structure:
{id: "0", isOpen: false, children: [
{id: "0.0", isOpen: false, children: [
{id: "0.0.0", isOpen: false, children: null},
{id: "0.0.1", isOpen: false, children: null}
]},
{id: "0.1", isOpen: false, children: null}
]}
The id for each child is the id of the previous parent, plus a "." and the number of order within that level.
You can then, with recursion, check if the desired item with some X id could be a child of an item, and start traversing that child the same way. This way, you don't have to traverse all items in the tree.
Of course, this may vary from application to application, but hopefully it provides a clue of how you could approach this.
The code for the recursion triggered by the click is as follows:
_toggleChildrenInit = (data_id) => {
var result = this._toggleChildren(0, data_id, this.state.listObject);
this.setState({listObject: result});
}
_toggleChildren = (i, data_id, arr) => {
if (i <= arr.length-1) {
if (arr[i].id == data_id && arr[i].children.length > 0) {
arr[i].isOpen = !arr[i].isOpen;
} else {
var newArr = this._toggleChildren(0, data_id, arr[i].children);
arr[i].children = newArr;
if (arr[i].id.charAt(0) != data_id.charAt(0)) {
arr[i].isOpen = false;
}
}
arr = this._toggleChildren(++i, data_id, arr);
}
return arr;
}
EDIT - alternative structure
If your object looks like this:
{id: "0", isOpen: false, children: [
{id: "1", isOpen: false, children: [
{id: "2", isOpen: false, children: null},
{id: "3", isOpen: false, children: null}
]},
{id: "4", isOpen: false, children: null}
]}
You can use this instead:
_toggleChildrenInit = (data_id) => {
var result = this._toggleChildren(0, data_id, this.state.listObject);
this.setState({listObject: result});
}
_toggleChildren = (i, data_id, arr) => {
if (i <= arr.length-1) {
if (arr[i].id == data_id && arr[i].children.length > 0) {
arr[i].isOpen = !arr[i].isOpen;
} else {
var newArr = this._toggleChildren(0, data_id, arr[i].children);
arr[i].children = newArr;
if (arr[i].id < data_id) {
arr[i].isOpen = false;
}
}
arr = this._toggleChildren(++i, data_id, arr);
}
return arr;
}