The function is getting the value of a button click as props. Data is mapped through to compare that button value to a key in the Data JSON called 'classes'. I am getting all the data correctly. All my console.logs are returning correct values. But for some reason, I cannot render anything.
I've tried to add two return statements. It is not even rendering the p tag with the word 'TEST'. Am I missing something? I have included a Code Sandbox: https://codesandbox.io/s/react-example-8xxih
When I click on the Math button, for example, I want to show the two teachers who teach Math as two bubbles below the buttons.
All the data is loading. Just having an issue with rendering it.
function ShowBubbles(props){
console.log('VALUE', props.target.value)
return (
<div id='bubbles-container'>
<p>TEST</p>
{Data.map((item,index) =>{
if(props.target.value == (Data[index].classes)){
return (
<Bubble key={index} nodeName={Data[index].name}>{Data[index].name}
</Bubble>
)
}
})}
</div>
)
}
Sandbox Link: https://codesandbox.io/embed/react-example-m1880
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
const circleStyle = {
width: 100,
height: 100,
borderRadius: 50,
fontSize: 30,
color: "blue"
};
const Data = [
{
classes: ["Math"],
name: "Mr.Rockow",
id: "135"
},
{
classes: ["English"],
name: "Mrs.Nicastro",
id: "358"
},
{
classes: ["Chemistry"],
name: "Mr.Bloomberg",
id: "405"
},
{
classes: ["Math"],
name: "Mr.Jennings",
id: "293"
}
];
const Bubble = item => {
let {name} = item.children.singleItem;
return (
<div style={circleStyle} onClick={()=>{console.log(name)}}>
<p>{item.children.singleItem.name}</p>
</div>
);
};
function ShowBubbles(props) {
var final = [];
Data.map((item, index) => {
if (props.target.value == Data[index].classes) {
final.push(Data[index])
}
})
return final;
}
function DisplayBubbles(singleItem) {
return <Bubble>{singleItem}</Bubble>
}
class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {
json: [],
classesArray: [],
displayBubble: true
};
this.showNode = this.showNode.bind(this);
}
componentDidMount() {
const newArray = [];
Data.map((item, index) => {
let classPlaceholder = Data[index].classes.toString();
if (newArray.indexOf(classPlaceholder) == -1) {
newArray.push(classPlaceholder);
}
// console.log('newArray', newArray)
});
this.setState({
json: Data,
classesArray: newArray
});
}
showNode(props) {
this.setState({
displayBubble: true
});
if (this.state.displayBubble === true) {
var output = ShowBubbles(props);
this.setState({output})
}
}
render() {
return (
<div>
{/* {this.state.displayBubble ? <ShowBubbles/> : ''} */}
<div id="sidebar-container">
<h1 className="sidebar-title">Classes At School</h1>
<h3>Classes To Search</h3>
{this.state.classesArray.map((item, index) => {
return (
<button
onClick={this.showNode}
className="btn-sidebar"
key={index}
value={this.state.classesArray[index]}
>
{this.state.classesArray[index]}
</button>
);
})}
</div>
{this.state.output && this.state.output.map(item=><DisplayBubbles singleItem={item}/>)}
</div>
);
}
}
ReactDOM.render(<Sidebar />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The issue here is ShowBubbles is not being rendered into the DOM, instead (according the sandbox), ShowBubbles (a React component) is being directly called in onClick button handlers. While you can technically do this, calling a component from a function will result in JSX, essentially, and you would need to manually insert this into the DOM.
Taking this approach is not very React-y, and there is usually a simpler way to approach this. One such approach would be to call the ShowBubbles directly from another React component, e.g. after your buttons using something like:
<ShowBubbles property1={prop1Value} <etc...> />
There are some other issues with the code (at least from the sandbox) that you will need to work out, but this will at least help get you moving in the right direction.
Related
I'm very new to coding and trying to figure out an issue I have come across.
I am using axios to pull a json file and store it in a state. (I am also using Redux to populate the form)
Then I am using .map() to dissect the array and show one value from within each object in the array.
example json:
unit :
[
{
designName : x,
quantity : 0,
},
{
designName : y,
quantity : 0,
},
{
designName : z,
quantity : 0,
}
]
I have then added an input to select the quantity of the value mapped and now I want to give that value back to the state, in order to send the entire modified json back to the API with Axios.
I feel like I'm close but I'm unsure what I need to do with the handleQuantity function.
Here's my code:
import React, { Component } from 'react';
import store from '../../redux_store'
import axios from 'axios';
import { Button, Card } from 'react-bootstrap'
import { Link } from 'react-router-dom'
store.subscribe(() => {
})
class developmentSummary extends Component {
constructor(props) {
super(props)
this.state = {
prjName: store.getState()[0].developmentName,
units: []
}
}
componentDidMount() {
axios.get('https://API')
.then(
res => {
console.log(res)
this.setState({
units: res.data.buildings
})
console.log(this.state.units.map(i => (
i.designName
)))
}
)
}
handleQuantity() {
}
render() {
return (
<>
<div className="Text2">
{this.state.prjName}
</div>
<div className="Text2small">
Please select the quantity of buildings from the list below
</div>
<ul>
{this.state.units.map((object, i) => (
<div className="Card-center">
<Card key={i} style={{ width: "50%", justifyContent: "center" }}>
<Card.Body>{object.designName}</Card.Body>
<Card.Footer>
<input
className="Number-picker"
type="number"
placeholder="0"
onChange={this.handleQuantity}
/>
</Card.Footer>
</Card>
</div>
))}
</ul>
Thanks in advance!
You have to pass the change event, unit object and the index to handleQuantity and then paste your changed unit as new object in between unchanged units.
Here is the code:
<input
className="Number-picker"
type="number"
placeholder="0"
onChange={(event) => this.handleQuantity(event, object, i)}
/>;
And the code for handleQuantity
handleQuantity = (event, unit, index) => {
const inputedNumber = +event.target.value; // get your value from the event (+ converts string to number)
const changedUnit = { ...unit, quantity: inputedNumber }; // create your changed unit
// place your changedUnit right in between other unchanged elements
this.setState((prevState) => ({
units: [
...prevState.units.slice(0, index),
changedUnit,
...prevState.units.slice(index + 1),
],
}));
}
I am trying to have a set of input fields right after one another like a terminal.
The Page component looks like this
import React, { Component } from "react";
import NewLine from "./newLine";
export class Page extends Component {
state = {
numberOfLine: 0,
lines: [{ value: "", id: 0, displayInputs: true}]
};
render() {
return (
<div className="container">
<div className="terminal">
<p className="prompt">
Hey there! This is a pre rendered line.
</p>
{this.state.lines.map(l => (
<NewLine
key={this.state.numberOfLine}
handelWhatever={this.handelWhatever}
line={l}
></NewLine>
))}
</div>
</div>
);
}
handelWhatever = (string_value, Tid) => {
// console.log(string_value, Tid);
// console.log(this.state.lines.filter(l => l.id != Tid));
const num = this.state.numberOfLine + 1;
this.setState({
numberOfLine: this.state.lines.length + 1,
lines: [
...this.state.lines.filter(line => line.id !== Tid),
{ value: string_value, id: Tid, displayInput: false },
{ value: "", id: num, displayInput: true }
]
});
};
export default Page;
and my NewLine component looks like this
import React, { Component } from "react";
export class NewLine extends Component {
state = {
id: this.props.line.id,
value: this.props.line.value,
displayInput: this.props.line.displayInput
};
render() {
return (
<React.Fragment>
<p className=output>
{this.state.displayInput && (
<input
type="text"
className="here"
value={this.state.value}
onChange={this.handleChange}
onKeyDown={this.handelEnter}
/>
)}
{this.state.value}
</p>
</React.Fragment>
);
}
handleChange = event => {
this.setState({ value: event.target.value });
};
handelEnter = event => {
if (event.key === "Enter") {
this.props.handelWhatever(event.target.value, this.state.id);
}
};
}
export default NewLine;
When I enter the "something" in the input it should make a NewLine component and delete the input from the previous one so that user can type on the newly rendered line that is why I have bool in the New Line state.
The states updates perfectly but when i user input it takes all the previous ones and render them, i.e,
Initial Stage
> Hey there! This is a pre rendered line.
>
User Input : 'ls'
> Hey there! This is a pre rendered line.
> ls
>
User Input : 'cd'
> Hey there! This is a pre rendered line.
> ls
> ls
> cd
and so on
I don't know what is going on I tried printing the state of the Parent component and it has desired number of lines In the map if I do console.log just after one input I will get
{value: "ls", id: 1, displayInput: false}
{value: "ls", id: 1, displayInput: false}
{value: "", id: 2, displayInput: true}
console logging in map is like this
{this.state.lines.map(l => {
console.log(l);
return (
<NewLine
key={this.state.numberOfLine}
handelWhatever={this.handelWhatever}
line={l}
></NewLine>
);
})}
You need to use the id of the line for the key for each NewLine component.
Also, you need to use {this.props.line.value} instead of {this.state.value} in the NewLine component.
See below
<React.Fragment>
<p className=output>
{this.state.displayInput && (
<input
type="text"
className="here"
value={this.state.value}
onChange={this.handleChange}
onKeyDown={this.handelEnter}
/>
)}
{this.props.line.value} OR {this.state.value}
</p>
</React.Fragment>
See this codepen.
In the code pen I use a div instead of React.Fragment but that's just because the fragment was throwing an error in a codepen.
EDIT
It actually works with this.state.value so depending on use case, both this.props.line.value and this.state.value works.
I am pulling down results from an API, like so:
const [state, setState] = React.useState({
matches: undefined,
chosenBets: [{}]
});
const API = "https://api.myjson.com/bins/i461t"
const fetchData = async (endpoint, callback) => {
const response = await fetch(endpoint);
const json = await response.json();
setState({ matches: json });
};
And rendering JSX based off it using the map() function:
export function MatchCardGroup(props) {
return (
<div>
{props.matches.map((match, i) => {
return (
<MatchCard
key={i}
matchCardIndex={i}
team_home={match.teams[0]}
team_away={match.teams[1]}
league_name={match.sport_nice}
odd_home={match.sites[0].odds.h2h[0]}
odd_draw={match.sites[0].odds.h2h[1]}
odd_away={match.sites[0].odds.h2h[2]}
onClick={props.onClick}
timestamp={match.timestamp}
/>
);
})}
</div>
);
}
I then have a card which has odds on it, each odd with its own click event:
export function MatchCard(props) {
const [state, setState] = React.useState({
selection: {
id: undefined
}
});
const {
timestamp,
team_home,
team_away,
league_name,
odd_away,
odd_draw,
odd_home,
onClick,
matchCardIndex,
selection
} = props;
const odds = [
{
id: 0,
label: 1,
odd: odd_home || 1.6
},
{
id: 1,
label: "X",
odd: odd_draw || 1.9
},
{
id: 2,
label: 2,
odd: odd_away || 2.6
}
];
const handleOnClick = (odd, oddIndex) => {
// need to changhe the selection to prop
if (state.selection.id === oddIndex) {
setState({
selection: {
id: undefined
}
});
onClick({}, matchCardIndex);
} else {
setState({
selection: {
...odd,
team_home,
team_away
}
});
onClick({ ...odd, oddIndex, team_home, team_away, matchCardIndex });
}
};
React.useEffect(() => {}, [state, props]);
return (
<div style={{ width: "100%", height: 140, backgroundColor: colour.white }}>
<div>
<span
style={{
...type.smallBold,
color: colour.betpawaGreen
}}
>
{timestamp}
</span>
<h2 style={{ ...type.medium, ...typography }}>{team_home}</h2>
<h2 style={{ ...type.medium, ...typography }}>{team_away}</h2>
<span
style={{
...type.small,
color: colour.silver,
...typography
}}
>
{league_name}
</span>
</div>
<div style={{ display: "flex" }}>
{odds.map((odd, oddIndex) => {
return (
<OddButton
key={oddIndex}
oddBackgroundColor={getBackgroundColour(
state.selection.id,
oddIndex,
colour.lime,
colour.betpawaGreen
)}
labelBackgroundColor={getBackgroundColour(
state.selection.id,
oddIndex,
colour.lightLime,
colour.darkBetpawaGreen
)}
width={"calc(33.3% - 8px)"}
label={`${odd.label}`}
odd={`${odd.odd}`}
onClick={() => handleOnClick(odd, oddIndex)}
/>
);
})}
</div>
</div>
);
}
In my App Component I am logging the returned object from the click event:
const onClick = obj => {
// check if obj exists in state.chosenBets
// if it exists, remove from array
// if it does not exist, add it to the array
if (state.chosenBets.filter(value => value == obj).length > 0) {
console.log("5 found.");
} else {
console.log(state.chosenBets, "state.chosenBets");
}
};
And what I want to do is this:
When the user clicks an odd of any given match, add that odd to chosenBets
If the user deselects the odd, remove that odd from chosenBets
Only 1 odd from each of the 3 possible odds of any match can be selected at any time
Bonus points: the selected odd is selected based on the global state from App, instead of local state. This is so if I edit the array elsewhere, it should update in the UI.
Any help would be greatly appreciated, I'm lost here!
Link to Codesandbox
I've taken a short look at your project, and here are a few pointers to help you out:
Objects are only equal by reference.
This means that
{ id: 0, matchCardIndex: 8 } === { id: 0, matchCardIndex: 8 }
is false, even if you expect it to be true. To compare them, you need to compare every key in the object:
value.id === obj.id && value.matchCardIndex === obj.matchCardIndex
This also affects the filter call you have in the index.tsx, so you should change the comparison there to something similar to
state.chosenBets.filter(value => value.id === obj.id && value.matchCardIndex === obj.matchCardIndex)
State should only live in one place
As you already mentioned, it would be better to keep the state in your index.tsx if it also you needed there, and don't keep it locally in the components further down the tree. I'd suggest having the components only render the state, and have handlers to change the state.
Example
Here's a fork of your code sandbox I think implements it in a way that you described: https://codesandbox.io/s/gifted-star-wg629-so-pg5gx
How can i push html into the last array. I was trying to add an item and supposed be add instantly into list array. The cod is working except I'm struggling to add new list into last array.
function addItem(id,name){
const array = JSON.parse(localStorage.getItem('categories'));
array.push({
name: name,
id:id,
});
//<li>{name}</li> push this into last array
localStorage.setItem('categories',JSON.stringify(array));
}
{categories.map(function(item, key){
return <div>
<ul>
<li>item.name</li>
</ul>
<button onClick={() => addItem(item.id,'value name')}>Add</button>
</div>
})}
Something looks wrong in your example. I have added a complete exampl. You can maintain localStorage and State both. I hope this example helps you.
You mistake is that while adding new item you are pushing it to localStoage due to which react dom does not get rerendered. You have to update the value of state for that.
class App extends React.Component {
constructor() {
super();
this.state = {
categories: [
{
name: "Hello",
id: 1
},
{
name: "World",
id: 2
}
]
};
this.addItem = this.addItem.bind(this);
this.SaveToLocalStorage = this.SaveToLocalStorage.bind(this);
}
SaveToLocalStorage() {
const categories = this.state.categories;
localStorage.setItem("categories", JSON.stringify(categories));
}
addItem(id, name) {
const categories = this.state.categories;
categories.push({
name: name,
id: id
});
this.setState({ categories });
//localStorage.setItem("categories", JSON.stringify(categories));
}
render() {
let categories = this.state.categories;
const test = categories.map(item => (
<div key={item.id}>
<li>{item.name}</li>
</div>
));
return (
<div>
{test}
<button onClick={() => this.addItem(Date.now(), "Item")}>
Click to Add More
</button>
<button onClick={() => this.SaveToLocalStorage()}>
Save To LocalStorage{" "}
</button>
</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById("root"));
<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>
I guess this is what you are asking for. You just need to set it to state and re-render it when ever you are trying to add an element to list/array. I don't know why you are setting it to local storage but you can do it from state directly if your intention is to just store the previous array for future additions.
import React, { Component } from "react";
class App extends Component {
state = {};
constructor(props){
super(props);
this.state={
arr = []
}
}
addItem(id, name) {
const array = JSON.parse(localStorage.getItem("categories"));
array.push({
name: name,
id: id
});
//<li>{name}</li> push this into last array
localStorage.setItem("categories", JSON.stringify(array));
this.setState({arr:array});
}
renderList = () => {
return this.state.array.map(function(item, key) {
return (
<div>
<ul>
<li>item.name</li>
</ul>
<button onClick={() => addItem(item.id, "value name")}>Add</button>
</div>
);
});
};
render() {
return <div>{this.renderList()}</div>;
}
}
export default App;
I have a very basic ReactJS app which uses Redux which contains the following components:
PanelMaterialSize > Select
/src/controls/PanelMaterialSize/PanelMaterialSize.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import './PanelMaterialSize.scss';
import Select from '../Select/Select';
import { setThemeList } from '../../store/AppConfig/actions';
class PanelMaterialSize extends Component {
componentDidMount() {
this.n = 1;
setInterval(() => {
let themeList = [
{ value: this.n, text: 'Option ' + this.n },
{ value: this.n + 1, text: 'Option ' + (this.n + 1) },
{ value: this.n + 2, text: 'Option ' + (this.n + 2) },
];
this.props.setThemeList(themeList);
this.n += 3;
}, 1000);
}
render() {
return (
<div className="partial-designer-panel-material-size">
<div>
<div className="label-input">
<div className="label">MATERIAL</div>
<div className="input">
<Select data={this.props.themeList} style={{ width: '100%' }} />
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (appState) => {
return {
themeList: appState.appConfig.themeList,
}
}
const mapDispatchToProps = (dispatch) => {
return {
setThemeList: (themeList) => dispatch(setThemeList(themeList)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PanelMaterialSize);
In my opinion the Redux logic is fine because I have tested by doing couple of things.
My problem is that when the render(...) method of: PanelMaterialSize gets called, the component: Select doesn't get rendered with the new data (which changes every one second).
Here you have a Codesandbox.io you can play with (preferable use Chrome):
https://codesandbox.io/s/03mj405zzv
Any idea on how to get its content changed properly?
If possible, please, provide back a new Codesandbox.io with your solution, forked from the previous one.
Thanks!
the problem is here in your select component.
you are passing initially empty array and checking your component with this.state.data props, next time reducer change your this.state.data will not update the data. because you initialize in constructor. constructor only invoke once when component mount.
SOLVED DEMO LINK
The Problem is in your select render method:
render() {
let data = this.state[this.name];
return (
<div className="control-select" {...this.controlProps}>
<div className="custom-dropdown custom-dropdown--grey">
<select className="custom-dropdown__select custom-dropdown__select--grey">
//change data with this.props.data
{this.props.data.length > 0 &&
this.props.data.map((elem, index) => {
return (
<option value={elem.value} key={index}>
{elem.text}
</option>
);
})}
</select>
</div>
</div>
);
}