React update child's props after parent's state change - reactjs

I'm trying to pass Draft.js's editor state from the editor component to my own Sidebar component.
Using the topmost component Notes I use a callback to get the editor state from CustomEditor and set it as the Notes state. I then pass that state to Sidebar as a prop.
The problem is that the prop is set before the callback fires. I was thinking a setTimeout but that seems rough. I'm aware of UNSAFE_componentWillReceiveProps() but the docs don't recommend it. Is there something in react for this use case?
export class Notes extends Component {
constructor(props) {
super(props);
this.getEditorState = this.getEditorState.bind(this)
this.state = {
editorState: "the placeholder data Sidebar should not have as a prop"
};
}
getEditorState(state) {
console.log(state)
this.setState({editorState: state})
}
render() {
return (
<section id="Notes">
<div id="editor-holder">
<Sidebar currentEditorState={this.state.editorState}/>
<div id="Editor">
<FileHeader />
<CustomEditor getState={this.getEditorState}/>
</div>
</div>
</section>
);
}
}
export default Notes;

The new Context API is the solution to this type of problem. Took a bit to get my head around it, but what I came up with gets editorState to Sidebar as a prop.
export const NoteContext = React.createContext("placeholderEditorState");
export class Notes extends Component {
constructor(props) {
super(props);
this.getEditorState = this.getEditorState.bind(this)
this.getFolderData = this.getFolderData.bind(this)
this.state = {
editorState: null,
folderData: null
};
}
getEditorState(state) {
this.setState({editorState: state});
}
getFolderData(data) {
this.setState({folderData : data})
}
render() {
return (
<section id="Notes">
<TopBar />
<div id="editor-holder">
<NoteContext.Provider value={{editorState: this.state.editorState}} >
<NoteContext.Consumer>
{(context)=>{ return (
<Sidebar currentEditorState={context.editorState} getFolderData={this.getFolderData}/>
)}}
</NoteContext.Consumer>
</NoteContext.Provider>
<div id="Editor">
<NoteContext.Provider value={{folderData: this.state.folderData}} >
<FileHeader />
</NoteContext.Provider>
<CustomEditor getState={this.getEditorState}/>
</div>
</div>
</section>
);
}
}
Looking at it now it seems very straightforward, that means I've learnt a lot! Let me know if I can improve anything here.

Well there are more possible options how to achieve this result
Conditional rendering
You can render <Sidebar> only when props has altered that menas
constructor(props)
super(props)
this.state = {
editorState: false
}
}
render() {
... {this.state.editorState && <Sidebar currentEditorState={this.state.editorState}/>}
}
Guard component for undefined/false props
Sidebar.js
render() {
if(!this.props.currentEditorState) return null // this will force React to render nothing
return ( ... )
}
Transition props to state with getDerivedState
https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
Sidebar.js
static getDerivedStateFromProps({ currentEditorState }, prevState) {
if(currentEditorState !== false {
return { currentEditorState }
} else {
return {}
}
}
render() {
(.... something bound to this.state.currentEditorState)
}
Use context (legacy context)
class Notes extends React.Component {
getEditorState(state) {
console.log(state)
this.setState({editorState: state})
}
getChildContext() {
return {
editorState: this.state.editorState
}
}
childContextTypes = {
editorState: PropTypes.oneOfType([PropTypes.obj, PropTypes.bool])
}
}
Sidebar.js
class Sidebar {
static contextTypes = {
editorState: PropTypes.oneOfType([PropTypes.obj, PropTypes.bool])
}
render() {
... this.context.editorState
}
}

Related

React componentDidUpdate() does not fire

I have an react app with primereact installed and I am using primereact/captcha.
Maybe I have misunderstood something, but isn't the following code supposed to work (console.log('Child component did update'))?
import React from 'react';
import { Captcha } from 'primereact/captcha';
export default function App() {
return (
<div className="App">
<ParentComponent/>
</div>
);
}
class Child extends React.Component {
componentDidUpdate () {
console.log('Child component did update');
}
render() {
return (<h2>Child component</h2>);
}
}
class ParentComponent extends React.Component {
constructor() {
super();
this.state = {
captchaSovled: false,
key : Math.random()
}
}
render() {
let output;
if (this.state.captchaSolved) {
output = <Child key={this.state.key} />;
} else {
output =<Captcha siteKey="xxxxxxx" onResponse={() => this.setState({ key : Math.random(), captchaSolved: true })} />
}
return (
<div>
<h1>Parent component</h1>
{output}
</div>
);
}
}
From React doc
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
In your code, the Child component is mounted after captchaSolved state is set, therefore only componentDidMount is fired on Child component.
componentDidUpdate is fired, if there is any change in the state or props. As of your component child:
class Child extends React.Component {
componentDidUpdate () {
console.log('Child component did update');
}
render() {
return (<h2>Child component</h2>);
}
}
There is no state or props which are changing, that's why componentDidUpdate never get's invoked.

How to set component state with context.state?

I recently ran into an issue with React Context.
I have some data stored in localstorage which I only intend to use when there is no data available from the context provider.
The data from localstorage is stored in my component's state.
I would like to override this.state if there is available data coming from context.
The struggle is that I don't know how to set the state when the context can only be used in the render method.
It is a very bad practice to call a setState in the render and I have no idea how to get the context.state outside of the render.
If there is no data from
There is some sample code below.
Any ideas are welcome which are taking me closer to the solution.
constructor(props) {
super(props);
this.state = {
data: ''
}
}
componentDidMount() {
this.setState({
data: localstorage.getItem('data')
})
}
render() {
return (
<>
<AppContext.Consumer>
{context => (
<>
{typeof context.state.data !== 'undefined'&&
<div>
{/*Print out data from this.state or from context.state*/}
</div>
}
</>
)}
</AppContext.Consumer>
</>
)
}
You can access to context outside render with following trick:
import { PageTitleContext } from '../lib/pageTitleProvider';
import { Helmet } from 'react-helmet';
import * as PropTypes from 'prop-types';
class PageTitle extends Component {
componentDidMount() {
if (this.props.title)
this.props.context.setTitle(this.props.title);
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props.title !== prevProps.title)
this.props.context.setTitle(this.props.title);
}
render() {
if (this.props.title) {
return <Helmet>
<title>{this.props.context.title}</title>
</Helmet>;
}
if (!this.props.title) {
return this.props.context.title;
}
}
}
PageTitle.propTypes = {
title: PropTypes.string,
context: PropTypes.object.isRequired,
};
export default (props) => (
<PageTitleContext.Consumer>
{(context) => <PageTitle {...props} context={context}/>}
</PageTitleContext.Consumer>
)

How to make a button in react.js to make a text toggle from appearing to disappearing?

I want to create a button so that when i click on it, it makes a text appear, and on second click, disappears, on third, appears again... and so on.
class App extends Component {
constructor() {
super();
this.state = {
isShow: false,
}
this.createText = this.createText.bind(this);
this.condrender = this.condrender.bind(this);
}
createText() {
this.setState({ isShow: true });
}
condrender() {
if (this.state.isShow===true) {
return (
<p>THIS TEXT</p>
);
}
}
render() {
return (
<div className="App">
<button onClick={this.createText}>Click</button>
{this.condrender()}
</div>
);
}
}
}
With this code, the text appears when i click on the button. So I added this line this.setState({isShow: false}), and I get an error.
condrender() {
if (this.state.isShow===true) {
this.setState({isShow: false})
return (
<p>THIS TEXT</p>
);
}
}
Warning: Cannot update during an existing state transition (such as
within render or another component's constructor). Render methods
should be a pure function of props and state; constructor side-effects
are an anti-pattern, but can be moved to componentWillMount
My thinking is that after I set it to false, the text will disappear since isShow's state will be false. Please help me understand the error and how to go around this?
This warning is shown, as you are calling a method in render method and then trying to set state in that method.
Instead, what you can do is :
class App extends Component {
constructor() {
super();
this.state = {
isShow: false,
}
this.createText = this.createText.bind(this);
this.condrender = this.condrender.bind(this);
}
createText() {
this.setState({ isShow: !this.state.isShow});
}
condrender() {
return (
<p>THIS TEXT</p>
);
}
}
render() {
return (
<div className="App">
<button onClick={this.createText}>Click</button>
{this.state.isShow ? this.condrender() : null}
</div>
);
}
}
}
Hope it helps.
It's pretty straight foward. On every click you just set the state to the opposite of itself. You can use a ternary to render.
import React from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
class App extends React.Component
{
constructor()
{
super();
this.state =
{
isShow: false,
}
this.ToggleText = this.ToggleText.bind(this);
}
ToggleText()
{
let state = { ...this.state };
state.isShow = !state.isShow;
this.setState(state);
}
render()
{
let element = <p>THIS TEXT</p>
return (
<div className="App">
<button onClick={this.ToggleText}>Click</button>
{
this.state.isShow ? element : null
}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Set state to the nagative of the previous state and all will be done !
createText() {
this.setState({ isShow: !this.state.isShow });
}

Remove a React component from the DOM

I have this piece of code (which I've simplified for posting here) that creates a component and renders it
const getComponentToRender = (user, path) => {
switch(path ){
case 'ChangePassword':
return <ChangePassword user={ user } />;
case 'NewPassword':
return <NewPassword user={ user } />;
case 'PasswordExpire':
return <PasswordExpire user={ user } />;
default:
return null;
}
}
class UserAdmin extends React.Component {
static propTypes = {
user: PropTypes.object.isRequired
};
render() {
const component = getComponentToRender(this.props.user, 'ChangePassword' );
return(
<div id='user-admin-wrapper'>
{component}
</div>
)
}
componentWillUnmount(){
}
}
When I navigate away from UserAdmin the componentWillUnmount gets called.
Q: What is the simplest way to actually remove the component ChangePassword or any other component (by its name) from the DOM when componentWillUnmount executes.
OR, removing the component at any point, without waiting for componentWillUnmount
Using react-dom 15.6.1 . btw
Un-mounting a component will un-mount(remove) all the child components it contains. So after componentWillUnmount the component you rendered inside it will be removed.
If you need to control over components that rendered without un-mounting you use conditional render logic.
Example
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
shouldIRender: true
};
}
componentDidMount() {
setTimeout(() => {
this.setState({shouldIRender: false});
}, 5000);
}
render() {
return(
<div>
<ComponentThatAlwaysHere />
{ this.state.shouldIRender === true ? <ComponentThatRemovesAfterStateChange /> : null }
{ this.state.shouldIRender === true && <AnotherComponentThatRemovesAfterStateChange /> }
</div>
)
}
}

React propTypes errors not showing

I'm having problems with React propTyoes. I'v created a component that require 2 props to work as you guys can see in the code below.
When I use the component in the App file, passing just 1 prop, without the "stateSidebarVisible" it doesn't throw me any error/warning from react...
(I read a lot of things about the NODE_ENV production/development, I searched in my node for process.env and didnt found the NODE_ENV variable by the way).
Any clue?
FFMainHeader
export default class FFMainHeader extends React.Component {
render() {...}
}
FFMainHeader.propTypes = {
stateSidebarVisible: React.PropTypes.bool.isRequired,
handleSidebarChange: React.PropTypes.func.isRequired
};
App
This is where i call the FFMainHeader component.
export default class FFMainApp extends React.Component {
.......
render() {
return (
<div id="FFMainApp">
<FFMainHeader
handleSidebarChange={this.onSidebarChange} />
<FFMainSidebar />
</div>
);
}
}
EDIT
export default class FFMainHeader extends React.Component {
constructor(props) {
super(props);
this.clickSidebarChange = this.clickSidebarChange.bind(this);
}
clickSidebarChange(e) {
e.preventDefault();
(this.props.stateSidebarVisible) ?
this.props.stateSidebarVisible = false :
this.props.stateSidebarVisible = true;
this.props.handleSidebarChange(this.props.stateSidebarVisible);
}
render() {
return (
<header id="FFMainHeader">
<a href="#" onClick={this.clickSidebarChange}>
Abre/Fecha
</a>
</header>
);
}
}
FFMainHeader.propTypes = {
stateSidebarVisible: React.PropTypes.bool.isRequired,
handleSidebarChange: React.PropTypes.func.isRequired
};

Resources