class App extends Component {
render() {
return (
........
{ let tags = this.state.tags;
for (var key in tags) {
<p className="h3 text-primary">{tags[key]}</p>
}}
.........
)
}
}
I have error: Failed to compile - Unexpected token. I don't understand where I have mistake. Help me please.
replace
{
let tags = this.state.tags;
for (var key in tags) {
<p className="h3 text-primary">{tags[key]}</p>
}
}
with
{Object.keys(tags).map(key => <p key={key} className="h3 text-primary">{tags[key]}</p>)};
First of all you don't have a state in your app. You can't use this.state.something if there is not a state.
Secondly, do not use for loops in your render function like this. There are better ways to do this as suggested in the other answer. But if you really want to do this:
class App extends React.Component {
state = {
tags: {
foo: "somefoo",
bar: "somebar",
baz: "somebaz"
}
};
render() {
const { tags } = this.state;
const arr = [];
for (var key in tags) {
arr.push(<p className="h3 text-primary">{tags[key]}</p>);
}
return (
arr
);
}
}
But, render method is so crowded, let's make a little bit cleaning.
class App extends React.Component {
state = {
tags: {
foo: "somefoo",
bar: "somebar",
baz: "somebaz"
}
};
getTags = () => {
const { tags } = this.state;
const arr = [];
for (var key in tags) {
arr.push(<p className="h3 text-primary">{tags[key]}</p>);
}
return arr;
}
render() {
return (
this.getTags()
);
}
}
But there are better ways to iterate over stuff like .map or Object(keys) then .map for your example as suggested.
class App extends React.Component {
state = {
tags: {
foo: "somefoo",
bar: "somebar",
baz: "somebaz"
}
};
render() {
const { tags } = this.state;
return (
Object.keys(tags)
.map(key => <p key={key}>{tags[key]}</p>)
);
}
}
But again, you can use a separate function to get a cleaner structure for your render method.
class App extends React.Component {
state = {
tags: {
foo: "somefoo",
bar: "somebar",
baz: "somebaz"
}
};
getTags = () => {
const { tags } = this.state;
return Object.keys(tags)
.map( key => <p key={key}>{tags[key]}</p>)
}
render() {
return (
this.getTags()
);
}
}
Nice and clean! Don't forget if you need other JSX in here you have to use {} to use javascript expressions. For example what if you want a wrapper div top of your p's?
class App extends React.Component {
state = {
tags: {
foo: "somefoo",
bar: "somebar",
baz: "somebaz"
}
};
getTags = () => {
const { tags } = this.state;
return Object.keys(tags)
.map( key => <p key={key}>{tags[key]}</p>)
}
render() {
return (
<div>
{ this.getTags() }
</div>
);
}
}
Related
For my website I want to include a feature that helps users randomly click a link programatically. The event happens in the parent component called StreamingPlaza, and its has a list of children components called StreamingCard, each containing a streaming link. Below is my code:
StreamingPlaza
class StreamingPlaza extends Component {
state = {
......
}
roomclicks = [];
componentDidMount() {
//Approach 1//
this.roomclicks[0].current.handleClick();
//Approach 2//
this.roomclicks[0].props.click = true;
......
}
setRef = (ref) => {
this.roomclicks.push(ref);
}
renderRoom = (room) => {
return <StreamingCard info={room} ref={this.setRef} click={false}></StreamingCard>;
}
render () {
const rooms = this.props.rooms;
return (
{ rooms && rooms.map (room => {
return this.renderRoom(room);
})
}
);
}
StreamingCard
class StreamingCard extends Component {
constructor(props){
super(props);
this.state = {
......
}
}
handleClick = () => {
document.getElementById("link").click();
}
render() {
return (
✔️ Streaming Link: <a id="link" href=......></a>
);
}
Regarding Approach 1, the console reported the error Cannot read property handClick of undefined. After I removed "current", it said that this.roomclicks[0].handleClick is not a function. Regarding Approach 2, I was not able to modify the props in this way, as the console reported that "click" is read-only.
Approach 1 is basically how its need to be done, but with React API.
See React.createRef
class StreamingPlaza extends Component {
roomclicks = React.createRef([]);
componentDidMount() {
// 0 is room.id
this.roomclicks.current[0].handleClick();
}
renderRoom = (room) => {
return (
<StreamingCard
info={room}
ref={(ref) => (this.roomclicks.current[room.id] = ref)}
click={false}
></StreamingCard>
);
};
render() {
const rooms = this.props.rooms;
return rooms.map((room) => {
return this.renderRoom(room);
});
}
}
I have an input tag component from react-tagsinput as follows:
const onTagChange = (tags) => {
const noDuplicateTags = tags.filter((v, i) => tags.indexOf(v) === i);
const duplicateEntered = tags.length !== noDuplicateTags.length;
if (duplicateEntered) {
onTagChange(tags);
console.log('duplicate');
}
onTagChange(noDuplicateTags);
};
function TagContainer({
tags,
}) {
return (
<div>
<Header>Meta:</Header>
<TagsInput value={tags} onChange={onTagChange} />
</div>
);
}
TagContainer.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string),
};
TagContainer.defaultProps = {
tags: [],
};
export default TagContainer;
and the implementation on the onTagChange method which is passed as a prop to the <TagContainer> component in another component.
export class Modal extends React.Component {
...
...
onTagChange = (tags) => {
this.props.onTagChange(tags);
}
...
...
render() {
return(
<TagContainer
tags={tags}
onTagChange={this.onTagChange}
/>
);
}
}
Problem: onlyUnique prop in the <TagsInput> component is set to true to avoid duplicate entries. But I need to display an error message saying "duplicate values" as soon as user enters a duplicate value. How can this be done especially on the third party component.
I think you're going to have to handle dealing with duplicates in your component because you are getting no feedback from <TagInput /> component.
At a higher level, I would do something like this
class Example extends React.Component {
constructor() {
super();
this.state = {
showDuplicateError: false
};
}
handleTagChange(tags) {
const uniqueTags = removeDuplicates(tags);
const duplicateEntered = tags.length !== uniqueTags.length;
if (duplicateEntered) {
this.showDuplicateError();
}
// add unique tags regardless, as multiple tags could've been entered
const { onTagChange } = this.props;
onTagChange(uniqueTags);
}
showDuplicateError() {
this.setState({
showDuplicateError: true
});
}
render() {
const { showDuplicateError } = this.state;
const { tags } = this.props;
return (
<React.Fragment>
{ showDuplicateError && <div>Duplicate entered</div>}
<TagsInput value={ tags } onTagChange={ this.handleTagChange } />
</React.Fragment>
);
}
}
I have an very simple piece of React code as im trying to understand the concepts.
In the code below if I comment out the setState function I can see that selectNumber runs when I expect it too. However when I try and change the state's numbersBurnt value I get an error.
class Game extends React.Component {
state = {
numbersBurnt: [1]
};
selectNumber = (clickedNumber) => {
console.log('This was run');
this.setState((prevState, props) => {
numbersBurnt = [1];
});
};
render() {
return /* Something */;
}
}
Error: Uncaught ReferenceError: numbersBurnt is not defined
UPDATE: Here is the full code:
function Stars(props) {
const starsRandNo = Math.ceil(Math.random()*9);
let stars = [];
for (let i=0; i < starsRandNo; i++) {
stars.push(<span key={i} className="star">{i+1}</span>)
}
return <div>{stars}</div>;
}
function StarsBtn(props) {
const starsBtnClick = () => {
alert('was clicked');
}
return <button onClick={starsBtnClick}>Change no of stars</button>
}
function NumberOptions(props) {
const no = 9;
let numbers = [];
const burnTest = (i) => {
if (props.numbersBurnt.indexOf(i) >= 0) {
return "number-options number-options--burnt";
} else {
return "number-options";
}
}
for (let i=0; i < no; i++) {
numbers.push(<span className={burnTest(i)}
onClick={() => props.selectNumber(i+1)} >{i+1}</span>)
}
return <div>{ numbers }</div>;
}
class Game extends React.Component {
state = {
numbersBurnt: [1]
};
selectNumber = (clickedNumber) => {
console.log(this.state.numbersBurnt);
this.setState((prevState, props) => {
numbersBurnt: [2]
}, () => { console.log(this.state.numbersBurnt)});
console.log(this.state.numbersBurnt);
};
render() {
return <div>
<Stars />
<br />
<StarsBtn />
<br />
<br />
<NumberOptions numbersBurnt={this.state.numbersBurnt}
selectNumber={this.selectNumber} />
</div>;
}
}
ReactDOM.render(
<Game />,
mountNode
);
You try it.
React state is specify in the constructor function.
class Game extends React.Component {
constructor(props){
super(props);
this.state = {
numbersBurnt: [1]
}
}
selectNumber = (clickedNumber) => {
console.log('This was run');
this.setState((prevState, props) => {
numbersBurnt: [1]
});
};
render() {
return /* Something */;
}
}
more simple.
class Game extends React.Component {
constructor(props){
super(props);
this.state = {
numbersBurnt: [1]
}
}
selectNumber = (clickedNumber) => {
console.log('This was run');
this.setState({
numbersBurnt: [1]
});
};
render() {
return /* Something */;
}
}
You can omit prevState, props.
In your setState , you should be using a : and not a =. Also remove the ; and the prevState and props in your setState as you are not making use of them
class Game extends React.Component {
state = {
numbersBurnt: [1]
};
selectNumber = (clickedNumber) => {
console.log('This was run');
this.setState({
numbersBurnt: [1]
}, () => { console.log(this.state.numbersBurnt)});
};
render() {
return /* Something */;
}
}
function Stars(props) {
const starsRandNo = Math.ceil(Math.random()*9);
let stars = [];
for (let i=0; i < starsRandNo; i++) {
stars.push(<span key={i} className="star">{i+1}</span>)
}
return <div>{stars}</div>;
}
function StarsBtn(props) {
const starsBtnClick = () => {
alert('was clicked');
}
return <button onClick={starsBtnClick}>Change no of stars</button>
}
function NumberOptions(props) {
const no = 9;
let numbers = [];
const burnTest = (i) => {
if (props.numbersBurnt.indexOf(i) >= 0) {
return "number-options number-options--burnt";
} else {
return "number-options";
}
}
for (let i=0; i < no; i++) {
numbers.push(<span className={burnTest(i)}
onClick={() => props.selectNumber(i+1)} >{i+1}</span>)
}
return <div>{ numbers }</div>;
}
class Game extends React.Component {
state = {
numbersBurnt: [1]
};
selectNumber = (clickedNumber) => {
this.setState({
numbersBurnt: [2]
}, () => { console.log(this.state.numbersBurnt)});
};
render() {
return <div>
<Stars />
<br />
<StarsBtn />
<br />
<br />
<NumberOptions numbersBurnt={this.state.numbersBurnt}
selectNumber={this.selectNumber} />
</div>;
}
}
ReactDOM.render(
<Game />,
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 have functional component GetWeather which I want to pass result of GetLocation function as props based on which GetWetaher will do something i.e. another get request (in the example below it only renders its props). I think it has to happen inside ComponentDidMount, not sure how to do it
function GetLocation() {
axios.get('http://ipinfo.io')
.then((res) => {
return res.data.loc;
})
}
function GetWeather(props) {
//more code here, including another get request, based on props
return <h1>Location: {props.location}</h1>;
}
class LocalWeather extends Component {
componentDidMount() {
//???
}
render() {
return (
<div >
<GetWeather location={GetLocation}/> //???
</div>
);
}
}
Update: So based on suggestion from Damian below is working for me
function GetWeather(props) {
return <h3>Location: {props.location}</h3>;
}
class LocalWeather extends Component {
constructor(props) {
super(props);
this.state = {
location: []
};
}
getLocation() {
axios.get('http://ipinfo.io')
.then((res) => {
this.setState({location:res.data.loc});
})
}
componentDidMount() {
this.getLocation();
}
render() {
return (
<div >
<GetWeather location={this.state.location}/>
</div>
);
}
}
You can do it alternatively also
constructor() {
super();
this.state = {
Location:[]
}
}
function GetLocation() {
axios.get('http://ipinfo.io').then((res) => {
this.setState ({
Location:res.data.loc;
});
});
}
function GetWeather(props) {
return <h1>Location: {this.props.location}</h1>;
}
class LocalWeather extends Component {
componentDidMount() {
//code
}
render() {
return (
<div >
<GetWeather location={this.GetLocation.bind(this)}/>
</div>
);
}
}
First off my graphql data model:
type Human {
id: !String,
name: !String,
children: [Human]
}
The only route (relay route config) I'm atm using:
class extends Relay.Route {
static queries = {
human: () => Relay.QL`query RootQuery { viewer }`
};
static routeName = 'AppHomeRoute';
}
The list component:
class HumanList extends Component {
render() {
let {children} = this.props.human;
let subListsHTML = human ? children.map(child => (
<HumanListItem key={child.id} human={child}/>
)) : '';
return <ul>{subListsHTML}</ul>;
}
}
export default Relay.createContainer(HumanList, {
fragments: {
human: () => Relay.QL`
fragment on Human {
children {
id,
${HumanListItem.getFragment('human')}
}
}
`
}
});
The list item component:
class HumanListItem extends Component {
state = {expanded: false};
render() {
let {human} = this.props;
let sublistHTML = '';
if (this.state.expanded) {
sublistHTML = <ul><HumanList human={human}/></ul>;
}
return (
<li>
<div onClick={this.onClickHead.bind(this)}>{human.name}</div>
{sublistHTML}
</li>
);
}
onClickHead() {
this.props.relay.setVariables({expanded: true});
this.setState({expanded: true});
}
}
HumanListItem.defaultProps = {viewer: {}};
export default Relay.createContainer(HumanListItem, {
initialVariables: {
expanded: false
},
fragments: {
human: (variables) => Relay.QL`
fragment on Human {
name,
${HumanList.getFragment('human').if(variables.expanded)}
}
`
}
});
Which runs fine for the root list. But as soon as I click on a ListItem and it is expanded, I get the following error:
Warning: RelayContainer: Expected prop 'human' supplied 'HumanList' to be data fetched by Relay. This is likely an error unless you are purposely passing in mock data that conforms to the shape of this component's fragment.
I can't make much sense of it, since the data I'm passing is not mocked but directly fetched by Relay as can be seen in the HumanList comp.
The error indicates that the <HumanList> is being rendered before its data is ready.
class HumanListItem extends Component {
onClickHead() {
this.props.relay.setVariables({expanded: true});
this.setState({expanded: true}); // <-- this causes the component to re-render before data is ready
}
Rather than using state, you can instead look at the current value of the variables:
class HumanListItem extends Component {
// no need for `state.expanded`
render() {
let {human} = this.props;
let sublistHTML = '';
if (this.props.relay.variables.expanded) {
// `variables` are the *currently fetched* data
// if `variables.expanded` is true, expanded data is fetched
sublistHTML = <ul><HumanList human={human}/></ul>;
}
return (
<li>
<div onClick={this.onClickHead.bind(this)}>{human.name}</div>
{sublistHTML}
</li>
);
}
onClickHead() {
this.props.relay.setVariables({expanded: true});
// no need for `setState()`
}
}
HumanListItem.defaultProps = {viewer: {}};
export default Relay.createContainer(HumanListItem, {
initialVariables: {
expanded: false
},
fragments: {
human: (variables) => Relay.QL`
fragment on Human {
name,
${HumanList.getFragment('human').if(variables.expanded)}
}
`
}
});