I want to render a child element based on the state in its parent. I tried to do the following (simplified version of the code):
class DeviceInfo extends Component {
constructor(props) {
super(props);
this.state = {
currentTab: "General",
};
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
}
navToggle(tab) {
this.setState({ currentTab: tab });
}
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
};
render() {
return (
<React.Fragment>
<div className="container">
<Nav className="nav-tabs ">
<NavItem>
<NavLink
className={this.state.currentTab === "General" ? "active" : ""}
onClick={() => {
this.navToggle("General");
}}
>
General
</NavLink>
</div>
{ this.tabsMap[this.state.currentTab] }
</React.Fragment>
);
}
}
But it did not work properly. Only when I put the contents of the tabsMap straight in the render function body it works (i.e. as a react element rather then accessing it through the object). What am I missing here?
Instead of making tabsMap an attribute which is only set when the component is constructed, make a method that returns the object, and call it from render:
getTabsMap() {
return {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
}
};
render() {
...
{ this.getTabsMap()[this.state.currentTab] }
...
}
You defining instance property with this.tabsMap (should be syntax error):
export default class App extends React.Component {
tabsMap = { General: <div>Hello</div> };
// Syntax error
// this.tabsMap = { General: <div>World</div> };
render() {
// depends on props
const tabsMapObj = {
General: <div>Hello with some props {this.props.someProp}</div>
};
return (
<FlexBox>
{this.tabsMap['General']}
{tabsMapObj['General']}
</FlexBox>
);
}
}
Edit after providing code:
Fix the bug in the constructor (Note, don't use constructor, it's error-prone, use class variables).
Moreover, remember that constructor runs once before the component mount if you want your component to be synchronized when properties are changed, move it to render function (or make a function like proposed).
class DeviceInfo extends Component {
constructor(props) {
...
// this.props.id not defined in this point
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={props.id}
/>
</React.Fragment>
}
render() {
// make a function to change the id
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
};
return (
<>
{ this.tabsMap[this.state.currentTab] }
</>
);
}
}
I think it's a this binding issue. Not sure if your tabsMap constant should have this in front of it.
Alternative answer... you can inline the expression directly in the render as
{ this.state.currentTab === 'General' && <GeneralCard id={this.props.id} /> }
Related
I am trying to iterate over an array and assign fields to corresponding child components.
The way I am currently doing it looks like this:
class CardExtension extends React.Component {
render() {
return (
<div>
{ this.props.value }
</div>
);
}
}
class Card extends React.Component {
render() {
return (
<div>
{ this.props.title }
</div>
);
}
}
Once child components are defined and imported, I do push new instances of these classes to a completely new array:
class CardContainer extends React.Component {
render() {
var arr = [
{
'id':1,
'title':'title',
'value':'test_value'
}
]
var elements=[];
for(var i=0;i<arr.length;i++){
elements.push(<Card title={ arr[i].value } />);
elements.push(<CardExtension value={ arr[i].title } />);
}
return (
<div>
{elements}
</div>
);
}
}
Is there any way to accomplish the same using the following format
class CardContainer extends React.Component {
render() {
var arr = [
{
'id':1,
'title':'title',
'value':'test_value'
}
]
return (
<div>
{arr.map((el, idx) => (
<Card title={ el.value } />
<CardExtension value={ el.title } />
))}
</div>
);
}
}
#update
The problem is that whenever I do use the latest solution, I do receive following error message: Adjacent JSX elements must be wrapped in an enclosing tag (39:24)
Working solution:
import React, { Component, Fragment } from 'react';
export default class CardContainer extends React.Component {
render() {
var arr = [
{
'id':1,
'title':'title',
'value':'test_value'
}
]
return (
<div>
{arr.map((el, idx) => (
<Fragment>
<Card title={ el.value } />
<CardExtension value={ el.title } />
</Fragment>
))}
</div>
);
}
}
The error message "Adjacent JSX elements must be wrapped in an enclosing tag" means just that: the <Card> and <CardExtension> elements need to be wrapped in a parent tag.
Now, you probably don't want to wrap the elements in a <div> (since it would create unnecessary DOM nodes), but React has a nifty thing called "Fragments", letting you group elements without extra nodes.
Here is a working solution for your example, using the fragment short syntax:
class CardContainer extends React.Component {
render() {
var arr = [{
'id':1,
'title':'title',
'value':'test_value'
}]
return (
<div>
{arr.map((el, idx) => (
<>
<Card title={ el.value } />
<CardExtension value={ el.title } />
</>
))}
</div>
);
}
}
I have a small part of my new React app which contains a block of text, AllLines, split into line-by-line components called Line. I want to make it work so that when one line is clicked, it will be selected and editable and all other lines will appear as <p> elements. How can I best manage the state here such that only one of the lines is selected at any given time? The part I am struggling with is determining which Line element has been clicked in a way that the parent can change its state.
I know ways that I can make this work, but I'm relatively new to React and trying to get my head into 'thinking in React' by doing things properly so I'm keen to find out what is the best practice in this situation.
class AllLines extends Component {
state = {
selectedLine: 0,
lines: []
};
handleClick = (e) => {
console.log("click");
};
render() {
return (
<Container>
{
this.state.lines.map((subtitle, index) => {
if (index === this.state.selectedLine) {
return (
<div id={"text-line-" + index}>
<TranscriptionLine
lineContent={subtitle.text}
selected={true}
/>
</div>
)
}
return (
<div id={"text-line-" + index}>
<Line
lineContent={subtitle.text}
handleClick={this.handleClick}
/>
</div>
)
})
}
</Container>
);
}
}
class Line extends Component {
render() {
if (this.props.selected === true) {
return (
<input type="text" value={this.props.lineContent} />
)
}
return (
<p id={} onClick={this.props.handleClick}>{this.props.lineContent}</p>
);
}
}
In your case, there is no really simpler way. State of current selected Line is "above" line collection (parent), which is correct (for case where siblings need to know).
However, you could simplify your code a lot:
<Container>
{this.state.lines.map((subtitle, index) => (
<div id={"text-line-" + index}>
<Line
handleClick={this.handleClick}
lineContent={subtitle.text}
selected={index === this.state.selectedLine}
/>
</div>
))}
</Container>
and for Line component, it is good practice to use functional component, since it is stateless and even doesn't use any lifecycle method.
Edit: Added missing close bracket
'Thinking in React' you would want to give up your habit to grab DOM elements by their unique id ;)
From what I see, there're few parts missing from your codebase:
smart click handler that will keep only one line selected at a time
edit line handler that will stick to the callback that will modify line contents within parent state
preferably two separate components for the line capable of editing and line being actually edited as those behave in a different way and appear as different DOM elements
To wrap up the above, I'd slightly rephrase your code into the following:
const { Component } = React,
{ render } = ReactDOM
const linesData = Array.from(
{length:10},
(_,i) => `There goes the line number ${i}`
)
class Line extends Component {
render(){
return (
<p onClick={this.props.onSelect}>{this.props.lineContent}</p>
)
}
}
class TranscriptionLine extends Component {
constructor(props){
super(props)
this.state = {
content: this.props.lineContent
}
this.onEdit = this.onEdit.bind(this)
}
onEdit(value){
this.setState({content:value})
this.props.pushEditUp(value, this.props.lineIndex)
}
render(){
return (
<input
style={{width:200}}
value={this.state.content}
onChange={({target:{value}}) => this.onEdit(value)}
/>
)
}
}
class AllLines extends Component {
constructor (props) {
super(props)
this.state = {
selectedLine: null,
lines: this.props.lines
}
this.handleSelect = this.handleSelect.bind(this)
this.handleEdit = this.handleEdit.bind(this)
}
handleSelect(idx){
this.setState({selectedLine:idx})
}
handleEdit(newLineValue, lineIdx){
const linesShallowCopy = [...this.state.lines]
linesShallowCopy.splice(lineIdx,1,newLineValue)
this.setState({
lines: linesShallowCopy
})
}
render() {
return (
<div>
{
this.state.lines.map((text, index) => {
if(index === this.state.selectedLine) {
return (
<TranscriptionLine
lineContent={text}
lineIndex={index}
pushEditUp={this.handleEdit}
/>
)
}
else
return (
<Line
lineContent={text}
lineIndex={index}
onSelect={() => this.handleSelect(index)}
/>
)
})
}
</div>
)
}
}
render (
<AllLines lines={linesData} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
I am still new to React Native and I struggle a bit with the programming paradigm. What I am trying to do (by following the structure of another React.js project) is to create a container (parent component) which contains a number of other components. My ultimate goal is to pass and handle all of the props in the parent component. Child component only shows them. My structure looks something like this:
export default class TemporaryCardRequestScreen extends React.Component {
constructor(props) {
super(props);
this.showDateTimePicker = this.showDateTimePicker.bind(this);
this.hideDateTimePicker = this.hideDateTimePicker.bind(this);
this.handleDatePicked = this.handleDatePicked.bind(this);
this.state.fromDateTime = {
isVisible: false,
value: new Date()
}
}
showDateTimePicker = () => { /*body*/ };
hideDateTimePicker = () => { /*body*/ };
handleDatePicked = date => { /*body*/ };
render() {
return (
<DateTimePickerComponent
isVisible={this.state.fromDateTime.isVisible}
onConfirmPressed={this.handleDatePicked}
onCancelPressed={this.hideDateTimePicker}
showDateTimePicker={this.showDateTimePicker}
value={this.state.fromDateTime.value}
/>
);
}
}
and the, my child component looks something like this:
// npm ref.: https://github.com/mmazzarolo/react-native-modal-datetime-picker
export default class DateTimePickerComponent extends Component {
constructor(props) {
super(props);
}
render() {
const {
isVisible,
onConfirmPressed,
onCancelPressed,
showDateTimePicker } = this.props;
return (
<>
<Button title="Show DatePicker" onPress={showDateTimePicker} />
<DateTimePicker
isVisible={isVisible}
onConfirm={onConfirmPressed}
onCancel={onCancelPressed}
mode='datetime'
is24Hour={false}
date={new Date()}
/>
</>
);
}
}
My focus now is on
onConfirmPressed={this.handleDatePicked}
currently, this.handleDatePicked accepts a single argument but I'd like it to accept one additional which is passed to it at the place being used in child component.
So, my ultimate goal would be to have something similar to this:
render() {
const {
isVisible,
onConfirmPressed,
onCancelPressed,
showDateTimePicker,
dateTimePickerId } = this.props;
return (
<>
<Button title="Show DatePicker" onPress={this.showDateTimePicker} />
<DateTimePicker
isVisible={isVisible}
onConfirm={onConfirmPressed(dateTimePickerId)}
onCancel={onCancelPressed}
mode='datetime'
is24Hour={false}
date={new Date()}
/>
</>
);
}
So, in this way, in my parent component I could have a single method which can handle the updates for a number of date time pickers in my container (This is actually my use-case). Instead of having the same type of handlers (with different property name) for pretty much the same thing.
UPDATE: Snack expo
You can capture 'onConfirm' of DateTimePickerComponent, then call parent function and pass in the dateTimePickerId.
TemporaryCardRequestScreen
// Modify to accept 2 arguments
handleDatePicked = (id, date) => {
if (id == "1") {
// code here
}
else if (id == "2") {
// code here
}
};
render() {
return (
<div>
<DateTimePickerComponent
isVisible={this.state.fromDateTime.isVisible}
onConfirmPressed={this.handleDatePicked}
onCancelPressed={this.hideDateTimePicker}
showDateTimePicker={this.showDateTimePicker}
value={this.state.fromDateTime.value}
dateTimePickerId="1"
/>
<DateTimePickerComponent
isVisible={this.state.fromDateTime.isVisible}
onConfirmPressed={this.handleDatePicked}
onCancelPressed={this.hideDateTimePicker}
showDateTimePicker={this.showDateTimePicker}
value={this.state.fromDateTime.value}
dateTimePickerId="2}
/>
</div>
);
}
DateTimePickerComponent
onMyCustomConfirmPressed = (date) => {
// Parent onConfirmPressed() shall accept 2 arguments
this.props.onConfirmPressed(this.props.dateTimePickerId, date)
}
render() {
const {
isVisible,
onConfirmPressed,
onCancelPressed,
showDateTimePicker,
dateTimePickerId } = this.props;
return (
<>
<Button title="Show DatePicker" onPress={this.showDateTimePicker} />
<DateTimePicker
isVisible={isVisible}
onConfirm={this.onMyCustomConfirmPressed}
onCancel={onCancelPressed}
mode='datetime'
is24Hour={false}
date={new Date()}
/>
</>
);
}
I've been working on learning React to see if it suits my organization's needs, so needless to say I'm new at it. I've got a sample app that I've been working on to see how it works. I've gone through several of the answers here and haven't found one that fixes my problem.
I'm running into the problem where I get a "Uncaught (in promise) TypeError: Cannot read property 'params' of undefined" in the "componentDidMount()" at "const { match: { params } } = this.props;" method in the component below. I have a very similar component that takes an id from the url, using the same method, and it works fine. I'm confused as to why one is working and another isn't. I'm probably just making a rookie mistake somewhere (perhaps more than one), any hints/answers are appreciated.
The routing:
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Route path='/' component={BaseView} />
<Route path='/test' component={NameForm} />
<Route path='/home' component={Home} />
<Route path='/quizzes' component={ViewQuizzes} />
<Route path='/comment/:rank' component={GetCommentsId} /*The one that works*//>
<Route path='/comment/edit/:testid' component={GetCommentEdit} /*The one I'm having trouble with*//>
<Route path='/comments' component={GetCommentsAll} />
</div>
</BrowserRouter>
);
}
}
The working component:
class GetCommentsId extends Component{
constructor (props) {
super(props)
this.state = {
Comments: [],
output: "",
wasClicked: false,
currentComment: " ",
}
this.handleCommentChange = this.handleCommentChange.bind(this);
}
componentDidMount(){
const { match: { params } } = this.props;
const url = 'http://localhost:51295/api/Values/' + params.rank;
axios.get(url).then(res => {
const comments = res.data;
this.setState({ comments });
this.output = (
<div>
<ul>
{ this.state.comments.map
(
comment =>
(<Comment
QuizId = {comment.Rank}
FirstName = {comment.FirstName}
Comments = {comment.Comments}
TestId = {comment.TestimonialId}
/>)
)}
</ul>
</div>
);
//console.log("From did mount: " + this.currentComment);
this.forceUpdate();
});
}
componentDidUpdate(){}
handleCommentChange(event){
//console.log("handle Comment Change activated");
}
handleClick(comment){
this.wasClicked = true;
this.currentComment = comment.Comments;
console.log(comment.Comments);
this.forceUpdate();
}
render () {
if(this.output != null){
if(!this.wasClicked){
return (this.output);
}
else{
console.log("this is the current comment: " + this.currentComment);
return(
<div>
{this.output}
<NameForm value={this.currentComment}/>
</div>
);
}
}
return ("loading");
}
}
The one that isn't working:
class GetCommentEdit extends Component {
constructor (props) {
super(props)
this.state = {
Comments: [],
output: "",
match: props.match
}
}
componentDidMount(){
const { match: { params } } = this.props;
const url = 'http://localhost:51295/api/Values/' + params.testid;
axios.get(url).then(res => {
const comments = res.data;
this.setState({ comments });
this.output = (
<div>
<ul>
{ this.state.comments.map
(comment =>
(<EditComment
QuizId = {comment.Rank}
FirstName = {comment.FirstName}
Comments = {comment.Comments}
TestId = {comment.TestimonialId}
/>)
)}
</ul>
</div>
);
//console.log("From did mount: " + this.currentComment);
this.forceUpdate();
});
}
render(){
return(
<div>
{this.output}
</div>
);
}
}
I've created a small app for you to demonstrate how to implement working react router v4.
On each route there is a dump of props, as you can see the params are visible there.
In your code I don't see why you are not using Switch from react-router v4, also your routes don't have exact flag/prop. This way you will not render your component views one after another.
Link to sandbox: https://codesandbox.io/s/5y9310y0zn
Please note that it is recommended to wrap withRouter around App component, App component should not contain <BrowserRouter>.
Reviewing your code
Please note that updating state triggers new render of your component.
Instead of using this.forceUpdate() which is not needed here, update your state with values you get from resolving the Promise/axios request.
// Bad implementation of componentDidMount
// Just remove it
this.output = (
<div>
<ul>
{this.state.comments.map
(
comment =>
(<Comment
QuizId={comment.Rank}
FirstName={comment.FirstName}
Comments={comment.Comments}
TestId={comment.TestimonialId}
/>)
)}
</ul>
</div>
);
//console.log("From did mount: " + this.currentComment);
this.forceUpdate();
Move loop function inside render method or any other helper method, here is code for using helper method.
renderComments() {
const { comments } = this.state;
// Just check if we have any comments
// Return message or just return null
if (!comments.length) return <div>No comments</div>;
// Move li inside loop
return (
<ul>
{comments.map(comment => (
<li key={comment.id}>
<Comment yourProps={'yourProps'} />
</li>
))}
</ul>
)
};
Add something like isLoading in your initial state. Toggle isLoading state each time you are done with fetching or you begin to fetch.
this.setState({ isLoading: true }); // or false
// Initial state or implement in constructor
state = { isLoading: true };
Render method will show us loading each time we are loading something, renderComments() will return us comments. We get clean and readable code.
render() {
if (isLoading) {
return <div>Loading...</div>
}
return (
<div>
{this.renderComments()}
</div>
);
}
I'm new in react-native and have some probems. In the fahterScreen I add some items to array and pass to childs as prop, I need that the child (CanastaScreen) update every 1seg and show the new value. I have the next code:
export default class CanastaScreen extends React.Component {
constructor(props) {
super(props);
setInterval( () => { this.render(); }, 1000);
};
render() {
return (
<Container>
<Content>
{this.props.screenProps.canasta.map( (item) => {
console.log(item.nombre);
return (
<Text>{item.nombre}</Text>
);
})}
</Content>
</Container>
);
}
}
Console output show correctly:
Item1
Item2
Item3
etc.
But the screen is always in blank. Some can help my about it ?
Thanks
First of all, you never should call render method of a component. in React Native, a component should update only if it's state changes. so if you have something like this :
<Parent>
<Canasta> ... </Canasta>
</Parent>
assuming that the changing variable is called foo in state of Parent, you need to pass it as prop to Canasta (child) and now by changing state of Parent (changing foo), Canasta should get updated. here's an example (calling updateFoo will update both Parent and Canasta):
class Parent extends Component {
constructor(props){
super(props); // it's recommended to include this in all constructors
this.state = { foo: initalValue } // give some value to foo
}
updateFoo(newValue){
this.setState({foo: newValue}) // setting state on a component will update it (as i said)
}
render() {
return(
<Canasta someProp={this.state.foo}> ... </Canasta>
)
}
}
}
After various changes, is found, the complete structure is: Parent(App.js) call children(Menu, Canasta). Menu allow add items to the shop-car and Canasta allow to sort and delete items. These are the important parts of the code:
App.js
export default class App extends React.Component {
constructor(props) {
super(props);
this.stateUpdater = this.stateUpdater.bind(this);
this.state = { canasta:[] };
}
render() {
return (
<View>
<RootNavigation data={[this.state, this.stateUpdater]} />
</View>
);
}
}
Menu.js
tryAddCanasta(index, plato){
let canasta = this.props.screenProps[0].canasta;
plato.id_Plato = canasta.length;
canasta.push(plato);
this.props.screenProps[1]('canasta', canasta);
}
Canasta.js
shouldComponentUpdate(nextProps, nextState) {
return true;
}
render() {
return (
<Container>
<Content>
<List>
{this.props.screenProps[0].canasta.map( (item) => {
return ( this._renderRow(item) );
})}
</List>
</Content>
</Container>
);
}
Special thanks to #Shadow_m2, now I don't need check every time, it works in "real time"