Toggle div-elements in different component in React - javascript

I'm fairly new to React. I'm trying to build a site where you can click navigation item (in this case music genre) and it will list all the songs that belongs to that particular genre.
My app.js looks like this:
import React, { Component } from 'react';
import ListGenres from './ListGenres';
import './App.css';
class App extends Component {
constructor(props) {
super();
this.state = {dataList: props.dataList};
}
render() {
return (
<div>
<div className="App">
<Navigation tracks = {this.state.dataList} />
<ListGenres tracks = {this.state.dataList}/>
</div>
</div>
);
}
}
export default App;
I have a navigation component that looks like this:
import React from 'react';
import HeaderBar from './HeaderBar';
import MenuItem from 'material-ui/MenuItem';
export class Navigation extends React.Component {
constructor(props) {
super();
}
/*
onClickFunction() {
toggle elements in another component
}
*/
render() {
const genres = this.props.tracks.map((elem) => {
return elem.genre;
});
const filtered = genres.filter((elem, index, self) => {
return self.indexOf(elem) === index;
});
const genreLoop = filtered.map((elem, i) => {
return (
<MenuItem
onClick= {this.onClickFunction}
key={ i }><a>{ elem }</a>
</MenuItem>);
});
return (
<div>
{ genreLoop }
</div>
);
}
}
export default Navigation;
My list of items are rendered in another component whick looks like this:
import React from 'react';
import './ListGenres.css';
export class ListGenres extends React.Component {
constructor(props) {
super();
}
render() {
return (
<div className="genreList">
<div className="tracklist-visible tracklist-pop">
<ul>
<h3>Pop</h3>
{ this.tracklist('Pop') }
</ul>
</div>
<div className="tracklist-visible tracklist-metal">
<ul>
<h3>Pop</h3>
{ this.tracklist('Metal') }
</ul>
</div>
</div>
);
}
Is there way to maybe add css-class to tracklist-div when anchor is clicked from Navigation-component? Looks like I can't pass any props from that component since it's "stand-alone"-component?

You need to lift the state up.
Of course, you can solve this with Redux too, but let's keep it simple and only use React.
Lifting State Up
Create a component that will contains both <Navigation /> and <ListGenres /> components.
Keep the state (genre and selectedGenre) in this parent component and pass it down through props.
You also need to create a callback to handle genres changes.
Here's the example:
class App extends Component {
constructor (props) {
super(props)
this.state = {
selectedGenre: null,
genres: [...]
}
}
onGenreChange (genre) {
this.setState({ selectedGenre: genre })
}
render () {
return (
<div>
<Navigation
onGenreChange={genre => this.onGenreChange(genre)}
genres={this.state.genres}
/>
<ListGenres
genres={this.state.genres}
selectedGenre={this.state.genres}
/>
</div>
)
}
}

You didn't supply much code or example on how things should work but as i understand you are looking for a behavior similar to Tabs, where you click a Tab and a corresponding View is presented.
If this is the case, then you need a Parent component that will manage the selected Tabs and render the View respectively.
This is a simple example:
const tabs = ["Pop", "Rock", "Rap", "Electro"];
const View = ({name}) => <h1 className="view">{`This is ${name} music!`}</h1>
class Tab extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { id, onClick } = this.props;
onClick(id);
}
render() {
const { name, id, isSelected } = this.props;
const css = `tab ${isSelected && 'selected'}`;
return (
<div
className={css}
onClick={this.onClick}
>
{name}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 1
}
this.onTabChange = this.onTabChange.bind(this);
}
onTabChange(id) {
this.setState({ selectedTab: id });
}
render() {
const { selectedTab } = this.state;
return (
<div>
{
tabs.map((t, i) => {
return (
<div className="wrapper">
<Tab name={t} id={i + 1} isSelected={selectedTab === i + 1} onClick={this.onTabChange} />
{selectedTab == i + 1 && <View name={t} />}
</div>
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
.tab {
display: inline-block;
margin: 0 10px;
width: 60px;
text-align: center;
cursor: pointer;
padding: 5px;
}
.selected {
border: 1px solid #eee;
box-shadow: 0 0 3px 1px #999;
}
.wrapper{
display:inline-block;
}
.view{
position: absolute;
top: 0;
left: 0;
margin-top: 50px;
text-align: center;
}
<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="root"></div>

Related

React onClick add class to clicked element but remove from the others

so what i try to achieve here is very similar to what is done here Transition flex-grow of items in a flexbox
But what i wonder how this could be done with React say i have this code
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
classNameToUse: ''
};
this.onElementClicked = this.onElementClicked.bind(this);
}
onElementClicked() {
this.setState({ classNameToUse : 'big-size'})
}
render() {
return (
<div>
<div className={this.state.classNameToUse} onClick={this.onElementClicked} >
something
</div>
<div className={this.state.classNameToUse onClick={this.onElementClicked} >
something else
</div>
</div>
);
}
}
This would of course add the className to them both but what i want to achieve is that one of them grows big with animation and the other collapse. And it sohuldnt matter if i have 2 or 10 elements
You can set active index on click:
// State
this.state = {
activeIndex: null
};
// event
onElementClicked(e) {
this.setState({ activeIndex: e.target.index })
}
// class to use
className={this.index === this.state.activeIndex ? 'big-size' : ''}
const { useState, useEffect } = React;
const App = () => {
const [divs,] = useState(['blue', 'green', 'black']);
const [selected, setSelected] = useState(null);
const onClick = (id) => {
setSelected(id);
}
return <div className="container">
{divs.map(pr => <div key={pr} style={{background: pr}} className={`box ${pr === selected ? 'selected' : ''}`} onClick={() => onClick(pr)}></div>)}
</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
.container {
display: flex;
height: 200px;
}
.box {
flex: 1;
cursor: pointer;
transition: all .3s ease-in;
}
.selected {
flex: 2;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<script src="https://unpkg.com/#material-ui/core#latest/umd/material-ui.development.js"></script>
<script src="https://unpkg.com/material-ui-lab-umd#4.0.0-alpha.32/material-ui-lab.development.js"></script>
<div id="root"></div>

Cannot set multiple images

I am getting buffered image/jpeg from backend. Parent component does setState every time a new image is received and is passed down to Child component.
Child Component receives every update and can be seen in console logs, but it is only displaying single image.
Parent component:
class App extends Component {
constructor(props) {
super(props);
this.state = {
nodeResponse: ''
}
}
getFileStatusFromNode = (data) => {
this.setState({nodeResponse: data})
}
render() {
let CardView = nodeResponse !== '' &&
<Card key={nodeResponse.fileName} name={nodeResponse.fileName} image={nodeResponse.buffer} />
return (
<div className="App tc">
{ CardView }
</div>
)
}
}
Child component:
class Card extends PureComponent {
constructor({props}) {
super(props);
this.state = {
src: '',
};
}
componentDidMount() {
console.log("Card mounted")
this.setState(prevState => ({
src: [this.props.image, ...prevState.src]
}), () => console.log(this.state.src, this.props.name));
}
render() {
const { name } = this.props;
const { src } = this.state;
return (
<a style={{width: 200, height: 250}} key={name} className={'tc'} >
<div id='images'>
<img style={{width: 175, height: 175}} className='tc' alt='missing' src={`data:image/jpeg;base64, ${src}`}/>
</div>
</a>
)
}
}
NOTE: These images are coming from socket io. I want to display them in real time rather than creating a list first and then display together.
You are only rendering 1 <Card> when defining the <CardView> component.
Assuming nodeResponse.imageFiles is an array of files, you should have something like the following:
class App extends Component {
constructor(props) {
super(props);
this.state = {
nodeResponse: ''
}
}
getFileStatusFromNode = (data) => {
this.setState({nodeResponse: data})
}
render() {
let CardView
if(this.state.nodeResponse !== '') {
CardView = this.state.nodeResponse.imageFiles.map(file => (
<Card
key={image.fileName}
name={image.fileName}
image={image.buffer} />
)
)}
return (
<div className="App tc">
{ CardView }
</div>
)
}
}
Try with
class App extends Component {
constructor(props) {
super(props);
this.state = {
nodeResponse: []
}
}
getFileStatusFromNode = (data) => {
// here you merge the previous data with the new
const nodeResponse = [...this.state.nodeResponse, data];
this.setState({ nodeResponse: nodeResponse });
}
render() {
return (<div className="App tc">
{this.state.nodeResponse.map(n => <Card key={n.fileName} name={n.fileName} image={n.buffer} />)}
</div>);
}
}
and in your child component
class Card extends PureComponent {
render() {
const { src } = this.props;
return (<a style={{width: 200, height: 250}} className="tc">
<div id='images'>
<img style={{width: 175, height: 175}} className='tc' alt='missing' src={`data:image/jpeg;base64, ${src}`}/>
</div>
</a>);
}
}

Prevent unnecessary re-render of React component

I have a PureComponent that renders another component and implements its onClick callback:
class ColorPicker extends React.PureComponent {
render() {
console.log('ColorPicker being rendered');
const fields = this.props.colors.map((color, idx) => {
const fieldProps = {
key: `${idx}`,
color,
/*onClick: () => { // PROBLEM HERE
this.props.colorPicked(color);
}*/
};
return <ColorField { ...fieldProps}/>;
});
return (
<div className="bla-picker">
<div>{`Refresh seed: ${this.props.seed}`}</div>
{fields}
< /div>
);
}
}
There is a small issue with this component: Whenever the ColorPicker is re-rendered, the nested ColorFields need to be re-rendered, too, because their onClick property changes each time. Using a lambda function will create a new instance of that function whenever the component is rendered.
I usually solve this by moving the implementation of onClick outside of the render method, like this: onClick: this.handleClick. However, I can't do this here, because the onClick handler needs to capture the color variable.
What's the best practice to solve this kind of problem?
Here's a jsfiddle to try it out; and as a snippet:
class ColorField extends React.PureComponent {
render() {
console.log('ColorField being rendered');
const divProps = {
className: 'bla-field',
style: {
backgroundColor: this.props.color
},
onClick: this.props.onClick
};
return <div { ...divProps}/>;
}
}
class ColorPicker extends React.PureComponent {
render() {
console.log('ColorPicker being rendered');
const fields = this.props.colors.map((color, idx) => {
const fieldProps = {
key: `${idx}`,
color,
/*onClick: () => { // PROBLEM HERE
this.props.colorPicked(color);
}*/
};
return <ColorField { ...fieldProps}/>;
});
return (
<div className="bla-picker">
<div>{`Refresh seed: ${this.props.seed}`}</div>
{fields}
< /div>
);
}
}
class Layout extends React.PureComponent {
constructor(props, ctx) {
super(props, ctx);
this.state = {
seed: 1
};
}
render() {
const pickerProps = {
colors: ['#f00', '#0f0', '#00f'],
colorPicked: (color) => {
console.log(`Color picked: ${color}`);
},
seed: this.state.seed
};
return (
<div>
<div
className="bla-button"
onClick = {this.btnClicked}
>
{'Click Me'}
</div>
<ColorPicker { ...pickerProps} />
</div>
);
}
btnClicked = () => {
this.setState({ seed: this.state.seed + 1 });
};
};
ReactDOM.render( <
Layout / > ,
document.getElementById("react")
);
.bla-button {
background-color: #aaa;
padding: 8px;
margin-bottom: 8px;
}
.bla-picker {}
.bla-field {
width: 32px;
height: 32px;
}
<div id="react">
</div>
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
As long as onClick remains commented out, only ColorPicker is re-rendered when the seed changes (see output from console.log). As soon as onClick is put in, all the ColorFields are re-rendered, too.
You can implement shouldComponentUpdate in your ColorField component like:
class ColorField extends React.Component {
shouldComponentUpdate(nextProps) {
return this.props.color !== nextProps.color;
}
render(){
const { color, onClick } = this.props;
console.log('Color re-rendered');
return (
<div
className="color"
onClick={onClick}
style={{
backgroundColor: color,
height: '50px',
width: '50px',
}}
/>
)
}
}
Example here
Be attentive as in the first solution we can use just React.Component because we implement shouldComponentUpdate by ourselves.

React switching between queues (lists) bug

I'm working on an app that keeps track of salespeople's availability based on being either "Available" or "With Client".
Here's the bug I'm having. I'll use an example:
2 salespeople have been added to the app. The order in which they have been added to the app seems to matter in a way I don't expect to. For example, if the first salesperson I've added is James and the second is Rick, If I click on the button next to Rick that reads "Helped A Customer", James will now populate both the "Available" and the "With Client" tables, and Rick will have disappeared.
However if I click on them in a certain order, it works fine. For example, in the same situation as the example above, if I click on James' "Helped A Customer" first, then Rick's "Helped A Customer", then James' "No Longer With Customer", then Rick's "No Longer With Customer", it behaves as expected.
Here's the github project, you can clone it and try it out yourselves:
https://github.com/jackson-lenhart/salesperson-queue
I'll post what I think is the most relevant code here as well:
main.js:
import React from "react";
import { render } from "react-dom";
import shortid from "shortid";
import deepCopy from "deep-copy";
import AddForm from "./add-form";
import Available from "./available";
import WithClient from "./with-client";
class Main extends React.Component {
constructor() {
super();
this.state = {
queue: {
available: [],
withClient: [],
unavailable: []
},
currName: ""
};
this.addToQueue = this.addToQueue.bind(this);
this.removeFromQueue = this.removeFromQueue.bind(this);
this.handleInput = this.handleInput.bind(this);
this.move = this.move.bind(this);
}
addToQueue(name) {
let newQueue = deepCopy(this.state.queue);
newQueue.available = this.state.queue.available.concat({
name,
id: shortid.generate()
});
this.setState({
queue: newQueue
});
}
removeFromQueue(id) {
let newQueue = deepCopy(this.state.queue);
for (let k in this.state.queue) {
newQueue[k] = this.state.queue[k].filter(x =>
x.id !== id
);
}
this.setState({
queue: newQueue
});
}
move(id, from, to) {
this.setState(prevState => {
let newQueue = deepCopy(prevState.queue);
let temp = newQueue[from].find(x => x.id === id);
newQueue[from] = prevState.queue[from].filter(x =>
x.id !== id
);
newQueue[to] = prevState.queue[to].concat(temp);
return {
queue: newQueue
};
});
}
handleInput(event) {
this.setState({
currName: event.target.value
});
}
render() {
return (
<div>
<AddForm
addToQueue={this.addToQueue}
handleInput={this.handleInput}
currName={this.state.currName}
/>
<Available
available={this.state.queue.available}
move={this.move}
removeFromQueue={this.removeFromQueue}
/>
<WithClient
withClient={this.state.queue.withClient}
move={this.move}
removeFromQueue={this.removeFromQueue}
/>
</div>
);
}
}
render(
<Main />,
document.body
);
add-form.js:
import React from "react";
class AddForm extends React.Component {
constructor() {
super();
this.clickWrapper = this.clickWrapper.bind(this);
}
clickWrapper() {
this.props.addToQueue(this.props.currName);
}
render() {
return (
<div>
<input
type="text"
onChange={this.props.handleInput}
/>
<button onClick={this.clickWrapper}>
<strong>Add To Queue</strong>
</button>
</div>
);
}
}
export default AddForm;
available.js:
import React from "react";
import Salesperson from "./salesperson";
class Available extends React.Component {
render() {
const style = {
item: {
padding: "10px"
},
available: {
padding: "20px"
}
};
let available;
this.props.available.length === 0 ?
available = (
<p>None available.</p>
) : available = this.props.available.map(x =>
<div key={x.id} style={style.item}>
<Salesperson
key={x.id}
id={x.id}
name={x.name}
move={this.props.move}
removeFromQueue={this.props.removeFromQueue}
parent={"available"}
/>
</div>
);
return (
<div style={style.available}>
<h1>Available</h1>
{available}
</div>
);
}
}
export default Available;
salesperson.js:
import React from "react";
import DeleteButton from "./delete-button";
import HelpedButton from "./helped-button";
import NlwcButton from "./nlwc-button";
class Salesperson extends React.Component {
render() {
const style = {
name: {
padding: "10px"
},
button: {
padding: "5px"
}
};
let moveButton;
switch(this.props.parent) {
case "available":
moveButton = (
<HelpedButton
move={this.props.move}
id={this.props.id}
style={style.button}
/>
);
break;
case "withClient":
moveButton = (
<NlwcButton
move={this.props.move}
removeFromQueue={this.props.removeFromQueue}
id={this.props.id}
style={style.button}
/>
);
break;
default:
console.error("Invalid parent:", this.props.parent);
}
return (
<div>
<span style={style.name}>{this.props.name}</span>
{moveButton}
<DeleteButton
removeFromQueue={this.props.removeFromQueue}
name={this.props.name}
id={this.props.id}
style={style.button}
/>
</div>
);
}
}
export default Salesperson;
helped-button.js:
import React from "react";
class HelpedButton extends React.Component {
constructor() {
super();
this.clickWrapper = this.clickWrapper.bind(this);
}
clickWrapper() {
this.props.move(this.props.id, "available", "withClient");
}
render() {
return (
<span style={this.props.style}>
<button onClick={this.clickWrapper}>
<strong>Helped A Customer</strong>
</button>
</span>
);
}
}
export default HelpedButton;
This was just a typo on my part. No longer an issue. Here's the commit that fixed the typo:
https://github.com/jackson-lenhart/salesperson-queue/commit/b86271a20ac8b700bec1e15e001b0c6ef57adb8b

Rendering of react components in unexpected way

I am new to react. I am trying to create a simple todolist using store2.js library. There is a problem in rendering the elements in that to do list.
var React = require('react');
var store = require("store2");
import {Button} from "react-bootstrap";
class CreateToDoList extends React.Component {
componentDidMount() {
this.inputVal="";
}
constructor() {
super();
this.addValueToList = this.addValueToList.bind(this);
this.handleInput=this.handleInput.bind(this);
};
handleInput(e)
{
this.inputValue=e.target.value;
}
addValueToList(){
if(store.has("todoList")===false)
{
store.set("todoList",{count:0})
}
var count=store.get("todoList").count;
count+=1;
var obj={
value:this.inputValue,
isChecked:false
};
store.transact("todoList",function(elements){
elements[count+""]=obj;
elements.count=count
});console.log(store.getAll("todoList"));debugger;
}
render() {
return ( <div>
<input type="input" onChange={this.handleInput}/>
<Button bsStyle="primary" onClick={this.addValueToList}>Add</Button>
<CreateShowPreviousTasks/>
</div>
)
}
}
class todoValues extends React.Component{
componentDidMount(){
this.handleClick=this.handleClick.bind(this);
}
handleClick(){
}
render(){
console.log(this.props)
return(
<div >
<input type="checkbox" checked={this.props.isCheck}></input>
<input type="input">{this.prop.value}</input>
</div>
)
}
}
class CreateShowPreviousTasks extends React.Component {
componentDidMount() {
console.log("here")
}
constructor(){
super();
this.handleClick=this.handleClick.bind(this);
}
handleClick(event)
{
}
render() {
if (store.has('todoList') !== undefined) {
var divElements=[];
this.loop=0;
var count=store.get("todoList").count;
for(this.loop=0;this.loop<count;this.loop++)
{
var obj=store.get("todoList");
obj=obj[count+""];
divElements.push(
<todoValues value={obj.value} key={this.loop+1}/>
)
}
} else {
store.set('todoList',{
count:0
})
}
return (<div>{divElements}</div>
)
}
}
export default CreateToDoList;
The class todoValues adds the div elements of two input buttons wrapped in a div. But the rendering is only done as <todovalues value="as"></todovalues>.
The class CreateShowPreviousTasks which retrieves the list of stored items in the local storage items and passing those values as properties to todoValues and wrapping as in a div.
use the folliwng syntax to render a list
render() {
let todos = store.get("todolist");
return (
<div>
{
Object.keys(todos).map(function(key){
let todo = todos[key];
return (<todoValues value={ todo.value } key={ key }/>)
})
}
</div>
}
Does this answer your question?

Categories