I am making a front end application using typescript and react. I have a component A which amongst other html elements has a textbox. I want to add this component A on click of a button. So if the user clicks the button multiple times, i want a new component A to be created on every click. Also I want to be able to store the text data so that I can later fetch it and process it.
I tried to make a list of this component but it gives me an error.
interface State {
componentList?: ComponentA[];
}
export class ComponentList extends React.Component<Props, State> {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
public onClick(event) {
const componentList = this.state.componentList;
this.setState({
componentList: componentList.concat(<ComponentA key=
{componentList.length} />)
});
}
public render() {
return (
<React.Fragment>
<button onClick={this.onClick}>Add component</button>
{this.state.componentList.map(function(component, index)
{
return ComponentA
})}
</React.Fragment>
);
}
}
You might want to make two changes in your code.
First initialise your state in the constructor,
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = { componentList: [] }
}
So that react can track that data.
Second thing is, you are returning wrong item from the map in the render function.
Try returning component, which is different copies of <ComponentA ../> that you pushed every time you clicked the button,
public render() {
return (
<React.Fragment>
<button onClick={this.onClick}>Add component</button>
{this.state.componentList.map(function(component, index)
{
return component;
})}
</React.Fragment>
);
}
Keep the component count in the state:
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
numComponents: 0
}
}
Add a new function which creates an array of the component for rendering later:
clickedComponents = () => {
let componentArray = [];
for (let i=0; i<this.state.numComponents; i++) {
componentArrays.push(<ComponentA />);
}
return componentArray;
}
Increment the component count with your onClick function:
public onClick(event) {
this.setState({numComponents: this.state.numComponents + 1});
}
Render the component array:
public render() {
return (
<React.Fragment>
<button onClick={this.onClick}>Add component</button>
{this.clickedComponents()}
</React.Fragment>
);
}
Related
I've got 2 components and want to get Panel_Menu element in another child component to do some stuff with it.
class Panel extends Component {
constructor(props){
super(props);
this.menuRef = React.createRef();
}
componentDidMount() {
console.log (this.menuRef.current)
// works correctly
}
render() {
return(
<>
<Panel_Menu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current}/>
</Panel_Menu>
</>
)
}
}
class Menu extends Component {
constructor(props) {
super(props);
}
isSame = () => {
const isSlideClass = this.props.item;
console.log(isSlideClass)
// is null
// expected output: → <div class="panel-menu"></div>
}
render() {
return (
<Left_Menu >
<Panel_Menu_Items className="test" onClick={this.isSame} />
</Left_Menu>
);
}
}
How can I update data in done render() to reach my goal?
Or... how can I get element instantly in external Component (Menu in this case) to do some stuff with it?
Issue
The issue here is that React refs, when attached on the initial render, will be undefined during the initial render. This means that item={this.menuRef.current} will enclose the initial undefined ref value in the click handler of the child.
Solution
It's simple, you really just need to trigger a rerender to reenclose an updated React ref value. You can either add some state to the Panel component and update it in the componentDidMount lifecycle method, or just issue a forced update.
class Panel extends Component {
menuRef = createRef();
componentDidMount() {
console.log(this.menuRef.current);
this.forceUpdate(); // <-- trigger rerender manually
}
render() {
return (
<>
<PanelMenu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current} />
</PanelMenu>
</>
);
}
}
Demo
I am developing a simple food app.
Firstly, it will show dishDetails in MenuComponent
and onClick it will pass Id of a selected dish to a function named as
getDish(dishdetail)
Here i want to send props or state to my CartComponent where it will show details of selected Dish.
Problem-1
Props is not passing to Cart (undefined value)
but dishdetail name,id is showing if i do console.log in MenuComponent
How i can pass props/state to Cart kindly guide me.
//Here im binding my function
class Menu extends Component {
constructor(props) {
super(props);
this.getDish = this.getDish.bind(this);
}
//This is my getDish function(in which i want to send props to Cart)
getDish(dishDetail) {
return (
<React.Fragment>
<Cart dishdetail={dishDetail}/> **//undefined in Cart**
{console.log({dishDetail.name})} **//it is working perfectly**
</React.Fragment>
);
}
Working Fine
From Where I am sending data onClick function
<button
onClick={() => this.getDish(this.props.dishes[index])}
></button>
All components should be rendered from render method. And there behaviour can be controlled using state.
// class Menu
constructor(props) {
super(props);
this.state = {
dishDetail: null
};
this.getDish = this.getDish.bind(this);
}
getDish(selectedDish) {
this.setState({
dishDetail: selectedDish
});
}
render() {
return (
<>
<button onClick={() => this.getDish(this.props.dishes[index]])}>Click Me</button>
{/*Cart is called from render and value passed from state*/}
<Cart dishdetail={this.state.dishDetail}/>
</>
);
}
And your cart class will be re-rendered with your new data
class Cart extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('cart', this.props);
return (
<div>You added {this.props.dishdetail} to cart</div>
);
}
}
I want to fetch data from server periodically and refresh rows when data is fetched by setState() but the rows doesn't re-render after setState().
constructor(props) {
super(props);
this.state = {
rows: []
}
this.refreshList = this.refreshList.bind(this);
}
refreshList() {
req.get('/data').end(function (error, res) {
// type of res is array of objects
this.setState({
rows: res
});
});
}
// call this method on button clicked
handleClick() {
this.refreshList();
}
render() {
return(
<div>
<button onClick={this.handleClick}>Refresh List</button>
<Table rows={this.state.rows}/>
</div>
);
}
when call refreshList() new feteched data doesn't render.
My table component is:
// Table component
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Thanks a lot for your help. How can I refresh list on click button?
Your table component never changes its state after the construction. You can fix it easily by updating the state from new props:
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
componentWillReceiveProps(newProps) {
this.setState({
rows: newProps.rows
});
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
However, if your table component is so simple, you can make it stateless and use props directly without setState():
export class Table extends Component {
render() {
return (
<div>
{this.props.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Note there is no need for constructor now. We could actually make it a functional component.
Use an arrow function:
req.get('/data').end((error, res)=> {
// type of res is array of objects
this.setState({
rows: res
});
});
With the ES5 style callback function, the context of this gets lost.
You could also bind this directly to a local variable, i.e., var that = this and stick with the function syntax, but I think most would agree what the ES6 arrow syntax is nicer.
I'm new to react.js, just follow the tutorial. Here is my code. At first, i tried to use the class Component 'Greeting' to let it show different words after
clicked the button, but i don't know what's wrong, it doesn't rerender the element, and the construtor() method of Greeting only called once. The commented out code functional Component 'Greeting' works well. Not sure what's the difference :(
class GreetingGuest extends React.Component {
render() {
return (
<h3>hello Guest, Click login button !!! </h3>
);
}
}
class GreetingUser extends React.Component {
render() {
return (
<h3>You have logged in, welcome !!!</h3>
);
}
}
class Greeting extends React.Component {
constructor(props) {
super(props);
console.log('Greeting.state.is_logon = ', props.is_logon);
this.state = {is_logon: props.is_logon};
}
render() {
let welcome_msg = null;
if (this.state.is_logon) {
welcome_msg = <GreetingUser />;
}else {
welcome_msg = <GreetingGuest />;
}
return welcome_msg;
}
}
//function Greeting(props) {
// const is_logon = props.is_logon;
// if (is_logon) {
// return <GreetingUser />;
// }
// return <GreetingGuest />;
//}
class LoginComponent extends React.Component {
constructor(props) {
super(props);
this.state = {is_logon: false};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
is_logon: !prevState.is_logon
}));
}
render() {
let button = null;
let greeting = null;
if (this.state.is_logon) {
button = (
<button onClick={this.handleClick}>Logout</button>
);
greeting = <Greeting is_logon={this.state.is_logon} />
}else {
button = (
<button onClick={this.handleClick}>Login</button>
);
greeting = <Greeting is_logon={this.state.is_logon} />
}
return (
<div>
{greeting}
{button}
</div>
);
}
}
ReactDOM.render(
<LoginComponent />,
document.getElementById('Login')
)
HTML:
<html>
<body>
<div id="Login"></div>
</body>
<html>
The reason the class component doesn't re render, is because you have stored the logged_in prop in state from the constructor, and the constructor is only called once. Also state can only be modified from within the component.
To fix this you have 2 options;
Use componentWillReceiveProps, and update the local state with the new logged_in prop.
componentWillReceiveProps(nextProps) {
if (nextProps.logged_in !== this.state.logged_in) {
this.setState({ logged_in: nextProps.logged_in });
}
}
Or; do not use state but use the prop directly.
render() {
let welcome_msg = null;
if (this.props.is_logon) {
welcome_msg = <GreetingUser />;
}else {
welcome_msg = <GreetingGuest />;
}
return welcome_msg;
}
Where I think you should use the latter, since the parent component already maintains state.
Well to be honest the answer which I posted previously was wrong. It was because the way you posted the question telling that everything works fine when function based component is added. Then I created a project using your code and figured out few issues in your code.
Firstly you are maintaining state locally outside redux here. You are passing down the state of the login from the parent LoginComponent to the child component called Greeting like this.
greeting = <Greeting is_logon={this.state.is_logon} />
This gets passed as a props to the child component which is Greeting in this case. Remember React uses one way data flow.
Now from that child component you can access the data using this.props as shown below. You don't need to maintain any local state what so ever there.
Do the following changes in your Greeting component.
constructor(props) {
super(props);
}
Then make sure you access the values from this.props instead of any local state object like this.
render() {
let welcome_msg = null;
if (this.props.is_logon) {
welcome_msg = <GreetingUser />;
}else {
welcome_msg = <GreetingGuest />;
}
return welcome_msg;
}
}
This solved the issue. Happy Coding !
I'd like to trigger the function handleDisplayProduct on click and pass to it the object clicked. So far it calls the function handleDisplayProduct when the list is generated for all the objects but the function is not triggered on the click event.
So how do i bind the event onclick with the Container and passing to it the element clicked?
Container
// Method to retrieve state from Stores
function getAllProductState(){
return {
products: ProductStore.getProducts(),
};
}
export default class ProductAllContainer extends Component {
constructor(props){
super(props);
this.state = getAllProductState();
}
handleDisplayProduct(data){
console.log(data);
// ProductActions.selectProduct(data)
}
render(){
const products = this.state.products;
return(
<div>
{ products.map(function(product,i){
return (
<ProductThumbnail
product = { product }
key = { i }
**onDisplayProduct = { this.handleDisplayProduct(product) }**
/>
)
},this)}
</div>
)
}
}
View
const ProductThumbnail = (props)=>{
return(
<div>
<LinksTo to="/" **onClick={props.onDisplayProduct}**>
<h1>{props.product.headline}</h1>
<img src={props.product.images[0].imagesUrls.entry[1].url} alt="Thumbnail small pic"/>
</LinksTo>
</div>
)
}
You need to bind the event listener to the react class. You can do it this way.
constructor(props){
super(props);
this.state = getAllProductState();
this.handleDisplayProduct = this.handleDisplayProduct.bind(this);
}
or alternatively using es6, you can use an arrow function instead.
handleDisplayProduct = (data) => {
console.log(data);
// ProductActions.selectProduct(data)
}
Note: Class properties are not yet part of current JavaScript standard. So the second example wouldn't work unless you add a babel-plugin-transform-class-properties babel plugin
Edit: Also #ryanjduffy pointed out a crucial mistake in your code. Refer his comment.