Setting the state with React + TypeScript - reactjs

I cannot figure out how to set the state of my React component with TypeScript.
I'm making a simple Todo list
I have a component for the entire list: TodoList
I want to seed the list with some items to play around with
I figured I'd send in a simple array as the props to the TodoList component, and then immediately set that as the state so I have something to work off of
import * as React from "react";
import { TodoItem, ITodoItem } from "../TodoItem";
interface ITodoListProps {
items: ITodoItem[];
}
interface ITodoListState {
stateItems: ITodoItem[];
}
export class TodoList extends React.Component<ITodoListProps, Partial<ITodoListState>> {
constructor(props: ITodoListProps) {
super(props);
// Trouble figuring this part out
// I'd like to set the state to the list from the
// props, as a seed.
this.setState({
stateItems: this.state.stateItems
});
}
public render() {
return (
<div>
<ul>
// This should probably be displaying the items from the state, and not the props.
{this.props.items.map((todo, i) => {
return <TodoItem key={i} name={todo.name} />
})}
</ul>
</div>
);
}
}

You need to pass the props.items to the state when constructing the initial state:
export class TodoList extends React.Component<ITodoListProps, Partial<ITodoListState>> {
constructor(props: ITodoListProps) {
super(props);
this.state = {
stateItems: props.items
};
}
...
}

In the constructor when setting the initial state, simply assign the value you want to this.state:
constructor() {
// ...
this.state = { stateItems: ... /* initial value */ }
}
Later in the lifecycle methods or event listeners you can change the state the way you are doing it in the constructor with setState

Related

Can a React component render another component stored in it's state?

I tried to create a container component that might change its inner component over time.
class Container extends React.Component {
constructor(props) {
super(props)
this.state = { content: new FooComponent() }
}
render() {
return <div>
{ this.state.content }
</div>
}
}
I get errors like:
Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state}). If you meant to render a collection of children, use an array instead.
I tried { [this.state.component] }, but get same error.
Are you able to put components from this.state into the JSX that is returned from render()? If not, how do you implement a container? I'm thinking of creating a container something like iOS's UINavigationController, where I can push and pop components in a stack, with only the top one rendered to the screen.
You can use JSX: this.state = { content: <FooComponent/> }
Which is React.createElement() behind the scenes.
class Container extends React.Component {
constructor(props) {
super(props)
this.state = { content: <FooComponent/> }
}
render() {
return <div>{this.state.content}</div>;
}
}
Or refer to Creating React Elements.
For reference here are more readable forms:
class Container extends React.Component {
state = {
content: <FooComponent />
};
render() {
return <>{this.state.content}</>;
}
}
function Container() {
const [content, setContent] = useState(<FooComponent />);
return <>{content}</>;
}
Can a React component render another component stored in it's state?
Yes you can!
There is two ways of doing it
1
import FooComponent from '..'
...
this.state = {
content: <FooComponent/>
}
...
render(){
return (
<div>{this.state.content}</div>
)
}
or
2
import FooComponent from '..'
...
this.state = {
content: FooComponent
}
...
render(){
const {
content: BarComponent
} = this.state
return (
<div>
<BarComponent />
</div>
)
}
I get errors like:
Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state}). If you meant to render a collection of children, use an array instead.
This happens because when you do new FooComponent() it returns an object and you can't render an object.
When using JSX, will be transpiled to React.createElement(FooComponent) and this is renderable.
When using only FooComponent, you store a reference to it and only in the render method you create a JSX that will become React.createElement(BarComponent)
The second aproach is good when the component is comming from props and you depending on the situation, you want to give it some props.
Working Example
The main question probably already answered there.
Notice: Limited functionality - storing only components will be a simple stateless view/screen stack/buffer.
The problem can be much wider. This solution won't be equal to saving previous states, changes ... no props/params, no history, no context, state reversing ... but shared common state (redux store) can be an advantage - no old states, autoupdating the whole chain of stored views.
class Container extends React.Component {
constructor(props) {
super(props)
this.state = { content: this.FooComponent() }
}
FooComponent(){
return(<FooComponent/>)
}
render() {
return <div>
{ this.state.content }
</div>
}
}

How to get a componenet instance in React

I want to know if there is possible to get the component instance as I need.
I put my new component as a children in the main state, but is no the same object in both files.
I need to reach children state in my MainComponent. Looking in google for componenet instance doesnt help, maybe I am out of focus and the name of this is different.
Here is my MainComponent:
import React, { Component } from 'react';
import AnotherComponent from './../whatever/AnotherComponent';
class MainComponent extends Component {
constructor(props) {
super(props);
this.state = {
children: [],
};
}
addChild() {
const { children } = this.state;
this.setState({
children: children.push(<AnotherComponent />)
});
}
getChildrenState(component) {
return component.state(); // this doesn't work!
}
render() {
const { children } = this.state;
return (
<div>
{(children.map(i => (<div key={this.getChildrenState(i).id}>{i}</div>))}
</div>
)
}
And This is AnotherComponent
import React, { Component } from 'react';
class AnotherComponent extends Component {
constructor(props) {
super(props);
this.state = {
id: 144,
};
}
render() {
return (
<div>
Here it is my cHild!
</div>
)
}
Putting <AnotherComponent/> to the state doesn't make sense because it's React element object that isn't associated with specific component instance.
Accessing children state in parent component breaks the encapsulation and indicates design problem.
The instance of class component should be retrieved with a ref, and doing so to access instance state is the last resort that may be needed to extend third-party components that don't provide desired functionality.
If AnotherComponent is first-party component, it should be designed accordingly, to not require state to be accessed from the outside:
render() {
return (
<div key={this.state.id}>{this.state.id}</div>
)
}
If the output needs to be more flexible, it can make use of render prop pattern:
render() {
const render = React.Children.only(children);
return (
<div key={this.state.id}>{render(this.state.id)}</div>
)
}
And used like:
<AnotherComponent>{id => <div>{id}</div>}</AnotherComponent>
If you want to access the state of the child component ( here AnotherComponent ) then you can either :
Maintain the state inside the AnotherComponent and pass the value to the parent ( here MainComponent ) on a change listener ( whenever the state changes ), or;
Maintain the state in the parent ( here MainComponent ) and pass the value to the child as prop.
Let me know if you want me to give an example implementation.

Sharing store change event between same hierarchical level child components

I am developing a simple React JS application for learning purpose. I just started learning React JS a few days ago. Now, I am having a problem with Flux Store. I need to share the change event across two child components on the same hierarchical level.
I have the parent component, called TodoComponent with the following definition
//Create
class TodoComponent extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div>
<div>
<ListComponent />
</div>
<AddItemComponent />
</div>
)
}
}
It has two child components called, ListComponent and the AddItemComponent. Moreover, I have a store with this definition.
import { EventEmitter } from 'events';
class DataStore extends EventEmitter{
constructor()
{
super();
this.todos = [
"Eat",
"Sleep",
"Die",
"Shower"
];
}
getAll(){
return this.todos;
}
addItem(newItem)
{
this.todos.push(newItem);
this.emit("change")
}
}
const dataStore = new DataStore;
export default dataStore;
It has a function for adding new item into the array and a function for fetching the array.
This is the ListComponent that is displaying the array of items from the DataStore flux store.
import React from 'react';
import TodoItem from './TodoItem';
import DataStore from './data.store';
class ListComponent extends React.Component{
constructor(props)
{
super(props)
this.state = { todos : DataStore.getAll() };
}
componentWillMount(){
DataStore.on('change', () => {
//do somethif
this.state = { todos : DataStore.getAll() };
})
}
render()
{
var deleteItem = (item) => {
this.deleteItem(item);
}
var editItem = (item) => {
this.editItem(item);
}
var addItem = (newItem) => {
this.addItem(newItem);
}
var todos = this.state.todos.map((item, index) => {
return (
<TodoItem item={item} addItem={addItem.bind(this)} deleteItem={deleteItem} editItem={editItem} />
)
});
return (
<ul>
{todos}
</ul>
)
}
deleteItem(item)
{
this.setState({ todos: this.state.todos.filter((listItem, index) => {
return listItem !== item;
}) });
}
editItem(item)
{
alert(item)
}
addItem(newItem)
{
DataStore.addItem(newItem);
}
}
module.exports = ListComponent;
It is updating the items in the change event of the DataStore store. But I am not calling the addItem function in the ListComponent. I am calling it in the AddItemComponent.
This is the definition of the AddItemComponent.
import React from 'react';
import DataStore from './data.store';
class AddItemComponent extends React.Component{
constructor(props)
{
super(props)
}
render()
{
return (
<form id="form-todo" onSubmit={this.addItem.bind(this)} action="post">
<input type='text' ref="newItem" />
<button>ADD</button>
</form>
);
}
addItem(e)
{
e.preventDefault();
DataStore.addItem(this.refs.newItem.value);
}
}
module.exports = AddItemComponent;
But when I trigger the addItem function in the AddItemComponent, the change event of the DataStore in the ListComponent is not triggered. Therefore, how can I synchronize the change event of Flux Store between two components exist on the same hierarchical level?
The solution I can think of is having the DataStore in the TodoComponent (parent component) and send the data and functions as props to the child component. I think, the code will become a bit messy in that way. Is that the only solution to do that?
Welcome to React! I recreated your example and your "change" event is firing in ListComponent, but to update the state in a component you should use this.setState(changes) rather than this.state = {changes}. Only use this.state = {} in the constructor to set the initial state. The setState method properly flows through the React lifecycle and causes the component to re-render using the new state. There is an official guide on React's state and lifecycle hooks here.

Pass State from component to parent

If have a Listing page that contains a table component:
class ListingPage extends Component {
static propTypes = {
getTableData: PropTypes.func.isRequired,
};
componentDidMount() {
this.props.getTableData(*I want to Pass in sortlist state here*);
}
render() {
return (
<div>
<Table />
</div>
}
And the Table component maintains a sortlist state:
class Table extends Component {
constructor(props) {
super(props);
this.state = {
sortlist: 'someStringData',
};
render() {
<div>
Table Information etc.
</div>
}
The sortlist is changed in the table component through various functions. How can I pass that sortlist state up to the ListingPage component?
Pass a function along to Table from ListingPage that gets called whenever the sortlist is changed.
ListingPage component:
class ListingPage extends Component {
static propTypes = {
getTableData: PropTypes.func.isRequired,
};
onSortChange(s) {
console.log(s);
}
render() {
return (
<div>
<Table onSortChange={s => this.onSortChange(s)} />
</div>
);
}
}
Table component:
class Table extends Component {
constructor(props) {
super(props);
this.state = {
sortlist: 'someStringData',
};
}
somethingThatTriggersSortListToChange(s) {
this.props.onSortChange(s);
}
render() {
return <div>Table Information etc.</div>;
}
}
Considering above answer, You can also use redux store in this case when you want to pass state from child to parent. Make an action call and store the state in redux store and get the state in parent component. This is another way of playing from child to parent component.
this.props.saveToStore(this.state.sortlist);
In your action file
cost SAVE_TO_STORE = “SAVE_TO_STORE”;
export function saveToStore(sortlist){
return {
type: SAVE_TO_STORE,
sortlist
}
}
Like store state in reducer and get the state in your parent component and return as props i.e., in mapStateToProps(state, props) function using redux connect method.

Child componenet not updating when parent component is updating its array in react

I am having a list of item in parent react component and in which i am added new item and updating items. Child component will receive the items in props and render it.
When parent state is getting updated , child component is not updating its value.
Do i need to update the state in child component state in "componentWillReceiveProps" ? What is the correct way of doing it.
Code Example
// parent component
import React, { Component } from "react";
import TestList from '../controls/testlistview'
export default class TestView extends Component {
constructor(props) {
super();
this.state = {
items: []
};
}
render() {
return (<div>
<button onClick={this.addItem.bind(this)}> Add item</button>
<button onClick={this.changeFirstItemText.bind(this)}> Change item</button>
<TestList items={this.state.items} index={index}/>
</div>);
}
addItem() {
var items = this.state.items.map(s=> s);
items.push('new one');
this.setState({
items: items
});
}
changeFirstItemText() {
var items = this.state.items.map(s=> s);
items[0] = "changed text";
this.setState({
items: items
});
}
}
//Child component
import React, { Component } from "react";
export default class TestList extends Component {
constructor(props) {
super();
debugger;
this.state = {
rootNodes: props.items
};
}
componentWillReceiveProps(nextProps){
debugger;
}
render() {
var items = this.state.rootNodes.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
}
Instead of
render() {
var items = this.state.rootNodes.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
you get the items from props
render() {
var items = this.props.items.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
You don't have to assign props to TestList state again, otherwise you will need to do setState() again from TestList in order to trigger the render again. (which is not necesary step)
http://codepen.io/kossel/pen/ObQLoR
In the TestList class you shouldn't assign the props to the Component's state - this is a surefire way to cause major issues in React, and is the cause of your issue here. See my answer here for why this is a bad idea.
If you change your TestItem to the following, then it should work fine.
export default class TestList extends Component {
constructor(props) {
super();
debugger;
}
componentWillReceiveProps(nextProps){
debugger;
}
render() {
var items = this.props.items.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
}
Reason is you are creating the ui element by the state of child component.
There are two ways of solving this issue:
1. update the state value in componentWillReceiveProps() funtion like this
componentWillReceiveProps(newProps){
this.setState({
rootNodes: newProps.items
});
}
2.Create the ui element directly from the props values like this-
render() {
var uiItems = this.props.items.map((item) => {
return <div>{item}</div>;
});
return (<div>{uiItems}</div>);
}

Resources