How to use the React Context API? - reactjs

I have a React App (setup with CRA) with a global element (custom cursor, that is a rounded div following the mouse) whose style I want to update/change when hovering various other components that are nested differently and more or less deeply (in the structure provided below I am only listing one example component). From what I understand this is a good use case for Context API.
The structure of my App looks like this (simplified):
<Cursor />
<Layout>
<Content>
<Item />
</Content>
</Layout>
So when hovering <Item /> (amongst other components) I want to update the style of the <Cursor /> component.
Therefore I tried to access a function I set up inside my <Cursor /> component in my <Item /> component. Unfortunately when hovering it does not update my state and as consequence the style of my <Cursor /> does not change.
My Cursor component looks like this (simplified):
import React, { Component } from "react"
export const CursorContext = React.createContext(false)
class Cursor extends Component {
constructor(props) {
super(props)
this.state = {
positionX: 0,
positionY: 0,
scrollOffsetY: 0,
display: "none",
isHoveringProjectTeaserImage: false,
}
this.handleMousePosition = this.handleMousePosition.bind(this)
this.handleMouseOverProjectTeaser = this.handleMouseOverProjectTeaser.bind(this)
this.handleMouseLeaveProjectTeaser = this.handleMouseLeaveProjectTeaser.bind(this)
}
handleMousePosition = (mouse) => {
this.setState({
positionX: mouse.pageX,
positionY: mouse.pageY,
display: "block",
scrollOffsetY: window.pageYOffset
})
}
handleMouseOverProjectTeaser = () => {
this.setState({
isHoveringProjectTeaserImage: true
})
}
handleMouseLeaveProjectTeaser = () => {
this.setState({
isHoveringProjectTeaserImage: false
})
}
componentDidMount() {
document.body.addEventListener("mousemove", this.handleMousePosition)
}
componentWillUnmount() {
document.body.removeEventListener("mousemove", this.handleMousePosition)
}
render() {
const {
positionX,
positionY,
display,
scrollOffsetY,
isHoveringProjectTeaserImage
} = this.state
return(
<CursorContext.Provider value={this.state}>
<div>
<StyledCursor
style={ isHoveringProjectTeaserImage
? {backgroundColor: "red", display: `${display}`, top: `${positionY - scrollOffsetY}px`, left: `${positionX}px`}
: {backgroundColor: "yellow", display: `${display}`, top: `${positionY - scrollOffsetY}px`, left: `${positionX}px`}}
/>
</div>
</CursorContext.Provider>
)
}
}
export default Cursor
And my Item Component that can be hovered looks like this (simplified):
import React, { Component } from "react"
import { CursorContext } from '../Cursor/Index';
class Item extends Component {
constructor(props) {
// not showing stuff in here that's not relevant
}
static contextType = CursorContext
render() {
return(
<CursorContext.Consumer>
{(value) =>
<StyledItem
onMouseOver={value.handleMouseOverProjectTeaser}
onMouseLeave={value.handleMouseLeaveProjectTeaser}
>
</StyledItem>
}
</CursorContext.Consumer>
)
}
}
export default Item
Do I even need to use static contextType = CursorContext?
When not passing a default value (I thought they are optional anyway) I am getting an TypeError: Cannot read property 'handleMouseOverProjectTeaser' of undefined, as soon as I pass a long a false as default value my App renders but does not update my <Cursor /> state.
Am I even using Context API correctly?

React.createContext default value?
As you correctly stated, the value passed to React.createContext() does not matter in this case.
When not passing a default value (I thought they are optional anyway) I am getting an TypeError: Cannot read property 'handleMouseOverProjectTeaser' of undefined, as soon as I pass a long a false as default value my App renders but does not update my state.
This brings out the fact that your default value is always used: try running undefined.blahblah vs. false.blahblah: the former throws a TypeError while the second silently returns undefined.
So we know the value you set in <Provider value={...}> never reaches the consumer, but why?
The Context is only available to its descendants
The <C.Consumer> is not rendered as a descendant of the <C.Provider>, so it can't get access to it. In other words, the provider should "enclose" the consumers. From the docs:
Context is designed to share data that can be considered “global” for a tree of React components [...]
The root of that tree is your <C.Provider>, and in your case, the consumer is not part of that tree.
Something like that could work:
<CursorContext>
<StyledCursor />
<Layout>
<Content>
<Item />
</Content>
</Layout>
</CursorContext>
Misc.
Do I even need to use static contextType = CursorContext?
Not really, if you're using <CursorContext.Consumer>. From the docs:
Context.Consumer: A React component that subscribes to context changes.
But in your case, since you don't need to listen to context changes (from your example code anyways), just keep the static contextType:
static contextType = CursorContext
render() {
return(
<StyledItem
onMouseOver={this.context.handleMouseOverProjectTeaser}
onMouseLeave={this.context.handleMouseLeaveProjectTeaser}
>
</StyledItem>
)
}
The point is you should use one or the other, you don't need both.
Last thing, you're passing this.state in the provider, and using this.context.handleMouseOverProjectTeaser in the child component... but there is no such function in <Cursor>'s state. Maybe you intended to pass <Cursor> itself, or better, just the handlers?

Related

Change HOC wrapper component with its own state to react hooks

I currently have an HOC component that I'd like to port over to use react hooks and basically just start thinking in that idea.
This HOC component basically provides a functionality for a wrapped component to display an alert dialog box. The HOC component manages its own state making this very easy for the wrapped component to display an alert dialog. The wrapped component simply has to call the function passed down to props to display it.
Here's what the HOC looks like now:
function withAlertDialog(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
alertDialogOpen: false,
alertMessage: ""
};
}
displayAlert = message => {
this.setState({alertDialogOpen: true, alertMessage: message});
}
closeAlertDialog = () => {
this.setState({alertDialogOpen: false});
}
render() {
return (
<React.Fragment>
<WrappedComponent
onDisplayAlert={this.displayAlert}
onCloseAlert={this.closeAlertDialog} />
<MyAlertDialogComponent
open={this.state.alertDialogOpen}
onClose={this.closeAlertDialog} />
</React.Fragment>
);
}
}
}
This is a more simple case, the actual HOC used is a lot more complex, but the idea still follows. The wrapped component can now basically call this.props.onDisplayAlert('some message here'); to display the alert. The wrapped component also doesn't have to render the MyAlertDialogComponent in its own render function. Basically, the wrapped component does not have to worry about how MyAlertDialogComponent is handled, all it knows is that calling this.props.onDisplayAlert will display an alert dialog box somehow. Reusing this HOC saves a lot of lines of code.
How would one go about changing this to a react hooks implementation? I've tried looking around but most articles and the documentation itself use an HOC with a single wrapped component and isn't really managing another component in addition to that. I'd like to understand how to change to the "react hooks" ideology but keep that same level of convenience about not having to render MyAlertDialogComponent in each component that wants to use it.
The only difference between your old HOC and a new HOC utilizing hooks is that you simply have to change the anonymous class you return from your HOC to an anonymous function that uses hooks.
Conversion between a class and a hooked-in function follows normal conversion rules that you might find in numerous tutorials online. In the case of your example, convert your state to useState and convert your class methods to regular functions.
You just pass the state and these regular functions around to whatever component needs them. Calling the setter for your state will re-render the component.
If you review the example below you'll see MyWrappedComponent is wrapped using withAlertDialog which passes the two function props to MyWrappedComponent. Those functions are used inside MyWrappedComponent to set the state that renders MyAlertDialogComponent
const { useState } = React
function withAlertDialog(WrappedComponent) {
return function(props){
const [alertState, setAlertState] = useState({
alertDialogOpen: false,
alertMessage: ""
})
const displayAlert = message => {
setAlertState({
alertDialogOpen: true,
alertMessage: message
});
}
const closeAlertDialog = () => {
setAlertState({alertDialogOpen: false});
}
return (
<React.Fragment>
<WrappedComponent
onDisplayAlert={displayAlert}
onCloseAlert={closeAlertDialog} />
<MyAlertDialogComponent
open={alertState.alertDialogOpen}
onClose={closeAlertDialog} />
</React.Fragment>
);
}
}
const MyWrappedComponent = withAlertDialog(function (props){
return (
<div>
<a onClick={props.onDisplayAlert}>Open Alert</a>
<a onClick={props.onCloseAlert}>Close Alert</a>
</div>
)
})
function MyAlertDialogComponent(props){
if(!props.open){
return null
}
return (
<div>Dialogue Open</div>
)
}
function App(){
return (
<MyWrappedComponent />
)
}
ReactDOM.render(<App />, document.querySelector('#app'))
div > a {
display : block;
padding : 10px 0;
}
<div id="app" />
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>

In React component lifecycle methods, `this.context` is an empty Object

Why is this.context an empty object, in this React component lifecycle methods?
The context has the correct value in the Consumer for that context. Only the this.context API is failing.
const LoremContext = React.createContext({
lorem: "ipsum",
})
class MenuItem extends React.Component {
componentDidMount() {
console.log(
"In MenuItem.componentDidMount, this.context is:",
this.context)
}
render() {
console.log(
"In MenuItem.render, this.context is:",
this.context)
return ( <LoremContext.Consumer>{
(lorem) => {
console.log("In LoremContext.Consumer, lorem is:", lorem)
return (
<li>
{ `Eat ${this.props.dish} at ${lorem}` }
</li>
)
}
}</LoremContext.Consumer> )
}
}
MenuItem.contextType = LoremContext
class Menu extends React.Component {
render() {
…
}
}
class Application extends React.Component {
render() {
return (
<LoremContext.Provider value={ this.props.world.lorem }>
<section className="app">
<Menu menuItems={ [ … ] } />
<p>Fusce varius id arcu egestas sodales</p>
</section>
</LoremContext.Provider>
)
}
ReactDOM.render(
<Application world={ { lorem: "consecteur" } } />,
document.getElementById('app-container'),
)
This is using React 16.4, so it makes use of the documented context API (introduced in React 16.3).
According to that documented API, the above code should get access to the context (defined in the return value from React.createContext) in two ways:
The LoremContext.Consumer component receives the context value passed by the LoremContext.Provider.
The consumer then provides that context value as an argument to the function within that component. In this case, lorem is the argument that receives the context value.
The this.context property receives (because of the declared MenuItem.contextType class property) the context value, inside the “lifecycle methods”.
Only one of those is working for me.
The LoremContext.Consumer API is getting and passing the context value correctly. The console.log output is:
In LoremContext.Consumer, lorem is: consecteur
The this.context is not getting the correct value, instead it gets an empty Object. The console.log output is:
In MenuItem.render, context is: Object { }
In MenuItem.componentDidMount, context is: Object { }
So the consumer is receiving the correct value, but this.context is not. Why the difference? How can I get the correct value received at this.context?
this.context was introduced in React 16.6 that you see can see here
Before this version, on 16.4, that you are using, accessing context inside React Lifecycles can be achieved:
class Button extends React.Component {
componentDidMount() {
// ThemeContext value is this.props.theme
}
componentDidUpdate(prevProps, prevState) {
// Previous ThemeContext value is prevProps.theme
// New ThemeContext value is this.props.theme
}
render() {
const {theme, children} = this.props;
return (
<button className={theme || 'light'}>
{children}
</button>
);
}
}
export default props => (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
See docs for more information
Try creating the context in a separate file and then importing it. That worked for me. When createContext was called in the same file where the <MyContext.Provider> tag was used, the consumers only saw an empty object. When I moved createContext to a separate file, the consumers saw the expected values. This applies to both methods of consuming - <MyContext.Consumer> and MyClass.contextType/this.context.
I'm afraid I can't explain why this works, but I found the solution in this thread.
Unfortunatelly without this part in the target component it is empty.
static contextType = ThemeContext; // assign the correct context
before created like
const ThemeContext = React.createContext('light');
https://reactjs.org/docs/context.html

Reactjs - passing state value from one component to another

I have two components SideNav and Dashboard (two are in different js files). SideNav will have selectbox as filters. I have to pass an array from Dashboard component to Sidebar component. This array has to given as values for select box (which is inside sidenav component).
P.S. What will be the case if I have two different component classes defined in two different JS files.
e.g. HomeComponent/Home.js -> Parent component
Dashboard/Dashboard.js -> Child component
I am making API call on "Home.js" file and getting some data. I want to pass these data to "Dashboard.js" file (component)
All the examples I studied, they show two components in the same JS file.
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {viz:{},filterData:{}};
}
var data1= ['1','2','3'];
this.setState({data1: data1}, function () {
console.log(this.state.data1);
});
}
//Sidebar
class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {
data: ['opt1','opt2']
};
}
handleClick(e) {
e.preventDefault();
e.target.parentElement.classList.toggle('open');
this.setState({data: this.state.data1}, function () {
console.log(this.state.data);
});
}
render() {
const props = this.props;
const handleClick = this.handleClick;
return (
<div className="sidebar">
<nav className="sidebar-nav">
<Nav>
<li className="nav-item nav-dropdown">
<p className="nav-link nav-dropdown-toggle" onClick={handleClick.bind(this)}>Global</p>
<ul className="nav-dropdown-items">
<li> Organization <br/>
<select className="form-control">
<option value="">Select </option>
{this.state.data.map(function (option,key) {
return <option key={key}>{option}</option>;
})}
</select>
If you have to pass state from Dashboard to Sidebar, you have to render Sidebar from Dashboard's render function. Here, you can pass the state of Dashboard to Sidebar.
Code snippet
class Dashboard extends Component {
...
...
render(){
return(
<Sidebar data={this.state.data1}/>
);
}
}
If you want the changes made on props (data1) passed to Sidebar be received by Dashboard, you need to lift the state up. i.e, You have to pass a function reference from Dashboard to Sidebar. In Sidebar, you have to invoke it whenever you want the data1 to be passed back to Dashboard.
Code snippet.
class Dashboard extends Component {
constructor(props){
...
//following is not required if u are using => functions in ES6.
this.onData1Changed = this.onData1Changed.bind(this);
}
...
...
onData1Changed(newData1){
this.setState({data1 : newData1}, ()=>{
console.log('Data 1 changed by Sidebar');
})
}
render(){
return(
<Sidebar data={this.state.data1} onData1Changed={this.onData1Changed}/>
);
}
}
class Sidebar extends Component {
...
//whenever data1 change needs to be sent to Dashboard
//note: data1 is a variable available with the changed data
this.props.onData1changed(data1);
}
Reference Doc : https://facebook.github.io/react/docs/lifting-state-up.html
You can only pass props from parent to child component. Either restructure your components hierarchy to have this dependence, or use a state/event management system like Redux (react-redux) .
I had the same issue with parent and child components and the solution was simply send down the function (which is altering the state in the parent component) as a prop to the child component. In this way both are sharing that particular variable's state. Hope this straightforward approach helps you!
I believe keeping the status values aligned with the page URL is another good way, not only to pass values, but also to keep the page status controllable with urls.
Imagine that you are building an advanced search page, where different components will control the search criteria, hence, in addition to search functionality, user should be able to keep his search settings by the used URL.
Supposing that clicking on a link in component x adds a query string criteria1=x to the current page url, and so on for the other components. Let's say we have also configured the search functionality to depend on the URL to read state values from it, this way, you will be able to pass values from a specific component to any number of components without restrictions.
This is called as props drilling.
You can pass data from one component to another by several ways
useContext Hook
Context API
Redux (Its a pattern)
Context with useContext hook, this is a better approach as using Context will increase the code complexity
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background,
color: theme.foreground
}}> I am styled by theme context!</button> );
}
For detailed information you can visit the links : useContext , Context and Redux
Redux is better for large-scale application, and if you have multiple Context always go for useContext hook.

Tabs only mount Tab content on the first time it becomes active

I would like to load the tab content only on the first time it becomes active, after that the content stays in the DOM
This is what I have
<Tabs defaultActiveKey={1} animation={false} id="my-tabs" mountOnEnter unmountOnExit>
<Tab eventKey={1}>
<div>content1</div>
</Tab>
<Tab eventKey={2}>
<div>content1</div>
</Tab>
</Tabs>
it works fine, but there is a lag between switching tabs, since the content I have is quite large and I would like to render it only once, on the first time the tab becomes active.
Is there a way to achieve that? I'm using react-bootstrap 0.30.10
UPDATE:
apparently mountOnEnter must be used with animation, otherwise it will not work as intended. I made the change and it works fine now
Old answer:
so I have come up with this wrapping component as follow
class TabsLazyLoad extends Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
this.handleSelect = this.handleSelect.bind(this);
}
getInitialState() {
return {
key: this.props.key || this.props.defaultActiveKey,
rendered: [],
};
}
addRenderedTab(key) {
const newState = _.cloneDeep(this.state);
newState.rendered.push(key);
this.setState(newState);
}
handleSelect(key) {
this.setState({ key });
}
render() {
return (
<Tabs activeKey={this.state.key} onSelect={this.handleSelect} {...this.props}>
{_.map(this.props.children, (tabComponent) => {
if (_.includes(this.state.rendered, tabComponent.props.eventKey)) {
return tabComponent;
}
if (tabComponent.props.eventKey === this.state.key) {
this.addRenderedTab(this.state.key);
}
// if it's not rendered, return an empty tab
const emptyTab = _.cloneDeep(tabComponent);
emptyTab.props.children = null;
return emptyTab;
})}
</Tabs>
);
}
}
TabsLazyLoad.propTypes = Tabs.propTypes;
It seems to be working fine, but I reckon this is a bit hacky, but it's the best I can come up with for now.
It sounds like a good use case for the "Avoid Reconciliation" option that React provides.
Here's a link to the relevant section in the documentation.
Essentially, there's a lifecycle event called shouldComponentUpdate that defaults to true. When you change it to false, it tells React not to run the component through the standard Reconciliation process (i.e. the "diff" checks).
Like with any lifecycle method, you can create a conditional statement for it.
For a component that should be made completely static after its first render, this is really all you need:
class YourComponent extends React.Component {
...
shouldComponentUpdate() {
return false;
}
...
}
However, for a more general use case, you'd want to write a conditional statement based on the props and/or the state of the component:
class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// Your state
};
}
shouldComponentUpdate(nextProps, nextState) {
// A conditional statement to determine whether
// this component should check for updates or not
}
render () {
return (
<div>
{/* Your JSX*/}
</div>
)
}
I don't use React Boostrap but I guess it's based on the Component design,
example, the rendered content used TabIndex state. Take a closer look at this sample code:
renderActiveTabContent() {
const { children } = this.props
const { activeTabIndex } = this.state
if (children[activeTabIndex]) {
return children[activeTabIndex].props.children
}
}
So the content component render every time Tab state is indexed.
You could use https://github.com/reactjs/react-tabs for your solution other wise take a look of those codes to write a simple one, the Component is rendered once and show/hide state via display: style attribute.
Hope it's help.

How should I be recreating a stateful child component in a parent's render method?

Facebook says that I should not keep a React component in its parent's state. Instead I should be recreating the child in render method each time it is run.
What Shouldn't Go in State?
React components: Build them in render() based on underlying props and
state.
Now my question is: How can I do that? Is it even possible? Isn't the state lost if I recreate a child component from scratch?
The only way I can think of that this scenario will work in, is that there's only one state object and it belongs to the root component. The rest of components will only have props and whenever they want to update some state of theirs, they need to call some parent's handler all the way up to root component, since it's the only component with an state object! And once updated, the root will give the child components back their state as props. Which I don't think it is practical at all!
[UPDATE]
Here's a sample code that I find hard not to store components in the parent's state:
http://codepen.io/mehranziadloo/pen/XdLvgq
class BlackBox extends React.Component
{
constructor() {
super();
this.state = {
counter: 0
};
}
increment() {
this.setState({ counter: this.state.counter+1 });
}
render() {
return (
<span onClick={this.increment.bind(this)} style={{
fontSize: '24pt',
border: '1px solid black',
margin: 10,
padding: 10,
}}>
{this.state.counter}
</span>
);
}
}
class RedBox extends React.Component
{
constructor() {
super();
this.state = {
counter: 0
};
}
increment() {
this.setState({ counter: this.state.counter+1 });
}
render() {
return (
<span onClick={this.increment.bind(this)} style={{
fontSize: '24pt',
border: '1px solid red',
margin: 10,
padding: 10,
}}>
{this.state.counter}
</span>
);
}
}
class Parent extends React.Component
{
constructor() {
super();
this.state = {
childCmps: [],
};
}
addBlackBox() {
let newState = this.state.childCmps.slice();
newState.push(<BlackBox key={newState.length} />);
this.setState({
childCmps: newState
});
}
addRedBox() {
let newState = this.state.childCmps.slice();
newState.push(<RedBox key={newState.length} />);
this.setState({
childCmps: newState
});
}
render() {
let me = this;
return (
<div>
<button onClick={this.addBlackBox.bind(this)}>Add Black Box</button>
<button onClick={this.addRedBox.bind(this)}>Add Red Box</button>
<br /><br />
{this.state.childCmps}
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
Isn't the state lost if I recreate a child component from scratch?
No, because React internally manages the backing instances (which hold the state) and does not replace them if two calls to render() say to render that component.
In other words:
ReactDOM.render(<MyComponent />, div);
ReactDOM.render(<MyComponent />, div);
This will not create MyComponent twice, but only once. It will render it twice: the first time it doesn't exist, so it creates it, and the second time it already exists, so it will update it. Any internal state that may be set between the two render passes will be preserved.
React is optimized to allow you to simply create complete, declarative render functions, and it figures out what changes are needed to actualize the rendering.
Update
The example you posted is using keys on a dynamic list of children. Keys are a way to identify specific children (and where they exist), so you need to be careful not to change keys between render passes for elements that maintain state.
Instead of storing the actual rendered components in state, such as <BlackBox key={i} />, store the necessary data to render the component, such as the component class BlackBox and a unique identifier for the key. (FYI you shouldn't use index as key, since index can change. I recommend using an always incrementing counter.)
Here is the Parent class modified to work without storing rendered components in state (the other components can remain as is):
class Parent extends React.Component {
static blackCount = 0;
static redCount = 0;
state = {
childCmps: [],
};
constructor(props, context) {
super(props, context);
}
addBlackBox = () => {
this.setState({
childCmps: [...this.state.childCmps, { Component: BlackBox, id: "black" + (++Parent.blackCount) }]
});
};
addRedBox = () => {
this.setState({
childCmps: [...this.state.childCmps, { Component: RedBox, id: "red" + (++Parent.redCount) }]
});
};
render() {
return (
<div>
<button onClick={this.addBlackBox}>Add Black Box</button>
<button onClick={this.addRedBox}>Add Red Box</button>
<br /><br />
{this.state.childCmps.map(child => <child.Component key={child.id} />)}
</div>
);
}
}
Example in CodePen.
Notes:
I used static (aka global) props to count how many black and red boxes have been added, combined with the strings "red" and "black" to form unique keys. (You can use Parent.blackCount = 0, etc, to initialize static class properties if you don't have support for class properties.)
I used fat arrow function properties as event handler callbacks to ensure this is in the correct scope. (You can use this.addBlackBox = this.addBlackBox.bind(this) in the constructor if you don't have support for class properties.)
I moved state initialization to a class property. As you can guess, I highly recommend you make use of class properties. :)
I used ES6 spread with array literal initialization to append a new box and create a new array.
Finally, in the Parent/render() function each box component is always re-rendered using a map() of the state with dynamic component type rendering of <child.Component>.
You only need to keep in parent's state any data necessary for rendering the children components. Typically, this is just the props you want pass, or the type of component.
In your case, this is just the color of the component "Red" or "Black".
So in parent state, an array containing Strings with value "Red" or "Black" is enough.
And everytime one of the buttons is clicked, you simply add another item to the array, and set state again. Something like this.
addRedBox() {
let newChildList = this.state.childList.slice();
newChildList.push("Red");
this.setState({
childList: newChildList
});
}
And then in your render() function do this:
{this.state.childList.map(function(color,i) {
if (color=="Black") {
return <BlackBox key={i} />
} else {
return <RedBox key={i} />
}
})}
On re-render, you simply pass new props (if any) to your child components, and each child component will then also re-render with the new props.
Passing new props to the child will not reset the child component. It will simply run all lifecycle methods again (including render()).
You can find a working version in this codepen.
the component only renders if the state changes(updates), and you should keep your state simple, use props to communicate with the children components.
and when your App gets larger you can use Flux or Redux to manage your states
You are attempting to see an Object-Oriented approach in React. Don't. There's OO and then there's whatever it is that Facebook do.
No, you cannot store components in state, as per the documentation you quoted. You can try it but you'll find things just don't work.
Here's an example of an OO class (in pseudocode):
class Parent {
list children
temporarilyAbondonChildren() {
for each child in children {
Orphanage.leaveAtDoorStep( child )
}
doStuffForTenYears()
for each child in Children {
output child.isOk()
}
}
}
And here's the closest equivalent to it in React:
class Parent {
temporarilyAbandonChildren() {
doStuffForTenYears()
child_properties = Orphanage.whatWouldveHappenedToMyKidHadIGivenBirthAndLeftThemForTenYears()
children = renderImaginaryChildren( child_properties )
for each child in children {
output child.isOk()
}
}
}
The only way I can think of that this scenario will work in, is that there's only one state object and it belongs to the root component. The rest of components will only have props and whenever they want to update some state of theirs, they need to call some parent's handler all the way up to root component, since it's the only component with an state object! And once updated, the root will give the child components back their state as props. Which I don't think it is practical at all!
I agree.

Resources