I am performing authentication on my top level route which I have named App in using react-router. My app is using the following
<div className="app-wrapper">
<NavbarC currentUser={this.state.currentUser} />
{this.props.children}
</div>
I want to be able to pass this.state.currentUser to the the this.prop.children.
Is this possible or should I just create a new state using the UserStore I've created?
Since I'm using flux, it seems the proper way to handle it is to pull the data from the store for each route. My route looks like the following:
let getCurrentUserFromStore = () => {
return { currentUser: UserStore.getCurrentUser() };
}
export default class Landing extends React.Component{
constructor(props) {
super(props);
this.state = getCurrentUserFromStore();
this.onStoreChange = this.onStoreChange.bind(this);
}
onStoreChange() {
this.setState(getCurrentUserFromStore());
}
componentDidMount() {
UserStore.addChangeListener(this.onStoreChange);
}
componentWillUnmount() {
UserStore.removeChangeListener(this.onStoreChange);
}
render() {
if ($.isEmptyObject(this.state.currentUser)) {
return <LoginPage />
} else {
return <DashboardPage currentUser={this.state.currentUser} />
}
}
}
Related
I am creating Higher Order Components in React and making sure that user cannot access the protected routes. Everything works fine but I am unsure where to put the code for checking the redux state. Currently, in the code below I have placed all the code in componentDidMount. This is because componentWillMount is deprecated. Is this the best place to check because componentDidMount is triggered after the component has been mounted.
import React, { Component } from 'react';
import { connect } from 'react-redux'
export default function(ComposedComponent) {
class Authenticate extends Component {
componentDidMount() {
if(!this.props.isAuthenticated) {
this.props.history.push('/')
}
}
render() {
return (
<ComposedComponent {...this.props} />
)
}
}
const mapStateToProps = (state) => {
return {
isAuthenticated: state.isAuthenticated
}
}
return connect(mapStateToProps)(Authenticate)
}
Assuming the correct state is available at construction, you can do the redirect in the constructor:
class Authenticate extends Component {
constructor(props) {
super(props);
if(!props.isAuthenticated) {
props.history.push('/')
}
}
...
}
This is the purpose of React Router Redirect component:
render() {
return !this.props.isAuthenticated ? (
<Redirect to="/" />
) : (
<ComposedComponent {...this.props} />
)
}
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
}
}
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>
)
}
}
Much of a basic question. Can I pass the state property to another component? So if I create a login app and after a successful login from API call I set the state of loggedInUser: 12345 in say a component called Login.js
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
data:[],
loggedInUser: 12345
}
}
render(){
return(
//Return the this.state.loggedInUser
)
}
}
How can I pass this.state.loggedInUser from Login.js to another component where I've imported Login.js?
For example, in my Page1.js I have import Login from './Login'
Can something like this be achieved? I just want to pass the this.state.loggedInUser value to any page where it us imported.
Thanks.
As mentioned in the comment above, redux is probably best practice here. But here is an example of accomplishing this with vanilla react.
export default class Parent extends Component {
constructor(props) {
super(props)
this.state = {
logginUser: undefined
}
this.handleUserState = this.handleUserState.bind(this);
}
handleUserState = (userInfo) => {
this.setState({logginUser: userInfo})
}
render = () => {
return (
<div>
<Login handleUserState={this.handleUserState} />
</div>
)
}
}
export default class Login extends Component {
constructor(props) {
super(props);
this.loginUser = this.loginUser.bind(this);
}
loginUser = (e) => {
e.preventDefault()
<--- make call to api for userInfo here and pass it to the call below --->
this.props.handleUserState(userInfo)
}
render =() => {
return(
<div>
<button type="submit" onClick={this.loginUser} />
</div>
)
}
}
Basically what's happening here since you are importing Login into another component, you will have that 'Parent' component act as the state manager and save the user data at that level. You then pass the function that updates the state to the Login component and call it once you have the user data to update it with.
I hope this helps!
After running this code - I got the exception that "title" is not defined. I checked that api returns correct data. And on the debug mode I noticed that render() from Idea component is running earlier than getting the data from API. Can you explain why is it working in this way? And what options I have for resolving this issue?
Thanks
'use strict';
const React = require('react');
const ReactDOM = require('react-dom');
const client = require('./client');
class App extends React.Component {
constructor(props) {
super(props);
this.state = {map: {}};
}
componentDidMount() {
client({method: 'GET', path: '/api/maps/1'}).done(response => {
this.setState({map: response.entity._embedded.map});
});
}
render() {
return (
<Map map={this.state.map}/>
)
}
}
class Map extends React.Component {
render() {
return (
<div id="map_header">
<AddIdeaButton></AddIdeaButton>
<Idea idea={this.props.map.root}></Idea>
</div>
);
}
}
class AddIdeaButton extends React.Component {
render() {
return (
<a id="btn_add">
</a>
);
}
}
class Idea extends React.Component {
render() {
<div id="root">{this.props.idea.title}</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('react')
);
Asynchronous request for data takes some time during which React still renders Map and Idea components. You can simply render Idea conditionally when data is available:
<div id="map_header">
<AddIdeaButton></AddIdeaButton>
{this.props.map.root && (
<Idea idea={this.props.map.root}></Idea>
)}
</div>