React: Alternatives to pushing methods up to parent component - javascript

I am working on a Rich Text Editor component using React and Draft.js that will be part of a library that my team uses to build our app's UI. I'm running into problems organizing the component in order to separate state and functionality. I'm not sure if it will matter to my questions, but we use Redux in our app but not in our library.
This is a version of the code that I have simplified to try to focus on the problems that I am having:
class TextInput extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: this.getInitialEditorState()
};
this.onChange = this.onChange.bind(this);
this.toggleLinkMenu = this.toggleLinkMenu.bind(this);
this.toggleInline = this.toggleInline.bind(this);
this.toggleBlock = this.toggleBlock.bind(this);
}
onChange(editorState) {
this.setState({ editorState });
}
toggleLinkMenu() {
this.setState({ displayLinkTooltip: !this.state.displayLinkTooltip });
}
toggleInline(style) {
this.setState({
editorState: RichUtils.toggleInlineStyle(this.state.editorState, this.options.style)
},
this.editor.focus
);
}
toggleBlock(blockType) {
this.setState({
editorState: RichUtils.toggleBlockType(this.state.editorState, this.options.blockType)
},
this.editor.focus
);
}
render() {
// While I'm developing, I have TextInput hardcoded to display every button in TextInputMenu:
const buttonList = ['bold', 'italic', 'underline', 'hyperlink', 'headline', 'blockquote'];
return (
<div className={styles.textInput} >
<TextInputMenu buttonList={buttonList} toggleInline={this.toggleInline} toggleBlock={this.toggleBlock} />
<Editor // This is the actual Draft.js Editor component where the user types their text
ref={c => (this.editor = c)}
editorState={this.props.editorState}
onChange={this.props.onChange}
placeholder={this.props.placeholder}
/>
</div>
);
}
}
// TextInputMenu */
// Contains a series of buttons that the user can click on to apply an inline
// style, apply a block style, or insert a hyperlink.
//
// The TextInput passes in a buttonList prop containing the names of the buttons
// that it wants to display.
class TextInputMenu extends React.Component {
constructor(props) {
super(props);
this.menuButtons = {
bold: { name: 'Bold', icon: 'fa-bold', options: /* Options here */ },
italic: { name: 'Italic', icon: 'fa-italic', options: /* Options here */ },
underline: { name: 'Underline', icon: 'fa-underline', options: /* Options here */ },
hyperlink: { name: 'Hyperlink', icon: 'fa-link', options: /* Options here */ },
headline: { name: 'Headline', icon: 'fa-header', options: /* Options here */ },
blockquote: { name: 'Block Quote', icon: 'fa-quote-left', options: /* Options here */ }
};
}
getMenuButtons() {
return _.pick(this.menuButtons, this.props.buttonList);
}
render() {
return (
<div className={styles.textInputMenu}>
{_.map(this.getMenuButtons(), ({name, icon, func}) => <TextInputMenuButton key={name} style={name} icon={icon} onClick={func} />)}
{this.getMenuButtons().find('hyperlink') && <LinkMenu />}
</div>
);
}
}
const TextInputMenuButton = ({style, icon, onClick}) => (
<div className={styles.textInputButton}>
<i onClick={() => onClick(style)} className={`fa ${icon}`} />
</div>
);
const LinkMenu = ({active, insertHyperlink}) => (
<Tooltip active={active}>
<form onSubmit={insertHyperlink}>
<label>URL:</label>
<input type="text" name="url" />
<label>Displayed Text:</label>
<input type="text" name="displayText" />
<Button />
</form>
</Tooltip>
);
export { TextInput };
export default TextInput;
Generally, I avoid using refs; however, I need to call the Editor component's "focus" method. Is there a better way to call a child component's methods other than using a ref?
I'm locating editorState in TextInput since it is used by its child components. However, this requires me to also locate methods that mutate editorState in TextInput even if locating them in a child component makes more sense. For example, I would like to move the methods that handle the behavior of TextInputMenu's buttons (toggleLinkMenu, toggleInline, and toggleBlock) to the Menu itself since, as I add more buttons, it is becoming increasingly unwieldy to pass around configuration and handle PropTypes. Is there some alternative pattern? I know the React ecosystem tends to favor Functional programming techniques, but it almost seems like it would be better to take a page from Object Oriented programming and pass a setter function for editorState to the Menu.

Is there a better way to call a child component's methods other than using a ref
I don't know about better, but if you don't want to use a ref, then you can rerender the parent (using a setState, for instance) that will pass a prop to the child - e.g. lift the state up - that you can then use in the componentDidUpdate method of the child. Compare the previous props to the current props and, if the prop you passed is different, then call the method.

Draft.js has a setter function like what you describe: onChange. You are passing it to the Draft.js <Editor>, and could pass it to <TextInputMenu> as well. This is a fairly common pattern when extending a Draft.js editor.
For your focus concern, if you haven't already read it have a look at the official documentation which covers why it is managed imperatively.
Looking at your example, you might also solve this by bind the toolbar buttons with onMouseDown instead of onClick – this way, with preventDefault() on the event, there won't be a focus shift when clicking the buttons and you won't need to call this.editor.focus(). This is for example done in the official rich text example:
class StyleButton extends React.Component {
constructor() {
super();
this.onToggle = (e) => {
e.preventDefault();
this.props.onToggle(this.props.style);
};
}
render() {
let className = 'RichEditor-styleButton';
if (this.props.active) {
className += ' RichEditor-activeButton';
}
return (
<span className={className} onMouseDown={this.onToggle}>
{this.props.label}
</span>
);
}
}

Related

Best way to access state of a React child class component in React parent functional component (react-dual-listbox)

I tend to use react functional components and hooks as I do not have a lot of experience with react. I want to use the react-dual-listbox class component within a parent functional component. Within this parent component I want to be able to access the selected state of the child class component. What is the best way to do this?
Child react-dual-listbox component from https://github.com/jakezatecky/react-dual-listbox
import React from 'react';
import DualListBox from 'react-dual-listbox';
const options = [
{ value: 1, label: 'Option One' },
{ value: 2, label: 'Option Two' },
];
class DualListChild extends React.Component {
state = {
selected: [1],
};
onChange = (selected) => {
this.setState({ selected });
};
render() {
const { selected } = this.state;
return (
<DualListBox
options={options}
selected={selected}
onChange={this.onChange}
/>
);
}
}
Contained within a standard functional component
function Parent() {
return(
<div>
<DualListChild/>
</div>
)
}
export default Parent;
Is it possible to, for example, have a hook in the parent component that changes state corresponding to what the dual listbox has selected? Essentially I want to pass state up but to a functional component? Is there a way to do this?
Thanks
You'd do something similar to what you do in DualListChild, except using the useState hook instead:
class DualListChild extends React.Component {
onChange = (selected) => {
this.props.onSelected(selected);
};
render() {
return (
<DualListBox
options={options}
selected={this.props.selected}
onChange={this.onChange}
/>
);
}
}
function Parent() {
const [selected, setSelected] = React.useState();
return (
<div>
<DualListChild selected={selected} onSelected={setSelected} />
</div>
)
}
Now you have access to selected (and even setSelected) inside your Parent component.
As an alternative way, you can have another state that keeps track of selected options in the parent and send its setter function to the child. Whenever the state of the child is changed, call the setter function that is coming from parent. With that way, the selected options state will be up-to-date value for the child and parent components any time.
function Parent() {
const [selectedOptions, setSelectedOptions] = useState([]);
return(
<div>
<DualListChild onSelectedOptionsChange={setSelectedOptions}/>
</div>
)
}
export default Parent;
class DualListChild extends React.Component {
...
onChange = (selected) => {
this.setState({ selected });
props.onSelectedOptionsChange(selected);
};
...
}

How to focus a specific component on page load in React using hooks? [duplicate]

What's the react way of setting focus on a particular text field after the component is rendered?
Documentation seems to suggest using refs, e.g:
Set ref="nameInput" on my input field in the render function, and then call:
this.refs.nameInput.getInputDOMNode().focus();
But where should I call this? I've tried a few places but I cannot get it to work.
#Dhiraj's answer is correct, and for convenience you can use the autoFocus prop to have an input automatically focus when mounted:
<input autoFocus name=...
Note that in jsx it's autoFocus (capital F) unlike plain old html which is case-insensitive.
You should do it in componentDidMount and refs callback instead. Something like this
componentDidMount(){
this.nameInput.focus();
}
class App extends React.Component{
componentDidMount(){
this.nameInput.focus();
}
render() {
return(
<div>
<input
defaultValue="Won't focus"
/>
<input
ref={(input) => { this.nameInput = input; }}
defaultValue="will focus"
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.js"></script>
<div id="app"></div>
Focus on mount
If you just want to focus an element when it mounts (initially renders) a simple use of the autoFocus attribute will do.
<input type="text" autoFocus />
Dynamic focus
to control focus dynamically use a general function to hide implementation details from your components.
React 16.8 + Functional component - useFocus hook
const FocusDemo = () => {
const [inputRef, setInputFocus] = useFocus()
return (
<>
<button onClick={setInputFocus} >
Focus
</button>
<input ref={inputRef} />
</>
)
}
const useFocus = () => {
const htmlElRef = useRef(null)
const setFocus = () => {htmlElRef.current && htmlElRef.current.focus()}
return [ htmlElRef, setFocus ]
}
Full Demo
React 16.3 + Class Components - utilizeFocus
class App extends Component {
constructor(props){
super(props)
this.inputFocus = utilizeFocus()
}
render(){
return (
<>
<button onClick={this.inputFocus.setFocus}>
Focus
</button>
<input ref={this.inputFocus.ref}/>
</>
)
}
}
const utilizeFocus = () => {
const ref = React.createRef()
const setFocus = () => {ref.current && ref.current.focus()}
return {setFocus, ref}
}
Full Demo
As of React 0.15, the most concise method is:
<input ref={input => input && input.focus()}/>
If you just want to make autofocus in React, it's simple.
<input autoFocus type="text" />
While if you just want to know where to put that code, answer is in componentDidMount().
v014.3
componentDidMount() {
this.refs.linkInput.focus()
}
In most cases, you can attach a ref to the DOM node and avoid using findDOMNode at all.
Read the API documents here: https://facebook.github.io/react/docs/top-level-api.html#reactdom.finddomnode
React 16.3 added a new convenient way to handle this by creating a ref in component's constructor and use it like below:
class MyForm extends Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focus();
}
render() {
return(
<div>
<input ref={this.textInput} />
</div>
);
}
}
For more details about React.createRef, you can check this article in React blog.
Update:
Starting from React 16.8, useRef hook can be used in function components to achieve the same result:
import React, { useEffect, useRef } from 'react';
const MyForm = () => {
const textInput = useRef(null);
useEffect(() => {
textInput.current.focus();
}, []);
return (
<div>
<input ref={textInput} />
</div>
);
};
The React docs now have a section for this. https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute
render: function() {
return (
<TextInput
ref={function(input) {
if (input != null) {
input.focus();
}
}} />
);
},
I just ran into this issue and I'm using react 15.0.1 15.0.2 and I'm using ES6 syntax and didn't quite get what I needed from the other answers since v.15 dropped weeks ago and some of the this.refs properties were deprecated and removed.
In general, what I needed was:
Focus the first input (field) element when the component mounts
Focus the first input (field) element with an error (after submit)
I'm using:
React Container/Presentation Component
Redux
React-Router
Focus the First Input Element
I used autoFocus={true} on the first <input /> on the page so that when the component mounts, it will get focus.
Focus the First Input Element with an Error
This took longer and was more convoluted. I'm keeping out code that isn't relevant to the solution for brevity.
Redux Store / State
I need a global state to know if I should set the focus and to disable it when it was set, so I don't keep re-setting focus when the components re-render (I'll be using componentDidUpdate() to check for setting focus.)
This could be designed as you see fit for you application.
{
form: {
resetFocus: false,
}
}
Container Component
The component will need to have the resetfocus property set and a callBack to clear the property if it ends up setting focus on itself.
Also note, I organized my Action Creators into separate files mostly due to my project is fairly large and I wanted to break them up into more manageable chunks.
import { connect } from 'react-redux';
import MyField from '../presentation/MyField';
import ActionCreator from '../actions/action-creators';
function mapStateToProps(state) {
return {
resetFocus: state.form.resetFocus
}
}
function mapDispatchToProps(dispatch) {
return {
clearResetFocus() {
dispatch(ActionCreator.clearResetFocus());
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyField);
Presentation Component
import React, { PropTypes } form 'react';
export default class MyField extends React.Component {
// don't forget to .bind(this)
constructor(props) {
super(props);
this._handleRef = this._handleRef.bind(this);
}
// This is not called on the initial render so
// this._input will be set before this get called
componentDidUpdate() {
if(!this.props.resetFocus) {
return false;
}
if(this.shouldfocus()) {
this._input.focus();
this.props.clearResetFocus();
}
}
// When the component mounts, it will save a
// reference to itself as _input, which we'll
// be able to call in subsequent componentDidUpdate()
// calls if we need to set focus.
_handleRef(c) {
this._input = c;
}
// Whatever logic you need to determine if this
// component should get focus
shouldFocus() {
// ...
}
// pass the _handleRef callback so we can access
// a reference of this element in other component methods
render() {
return (
<input ref={this._handleRef} type="text" />
);
}
}
Myfield.propTypes = {
clearResetFocus: PropTypes.func,
resetFocus: PropTypes.bool
}
Overview
The general idea is that each form field that could have an error and be focused needs to check itself and if it needs to set focus on itself.
There's business logic that needs to happen to determine if the given field is the right field to set focus to. This isn't shown because it will depend on the individual application.
When a form is submitted, that event needs to set the global focus flag resetFocus to true. Then as each component updates itself, it will see that it should check to see if it gets the focus and if it does, dispatch the event to reset focus so other elements don't have to keep checking.
edit
As a side note, I had my business logic in a "utilities" file and I just exported the method and called it within each shouldfocus() method.
Cheers!
This is not longer the best answer. As of v0.13, this.refs may not available until AFTER componentDidMount() runs, in some odd cases.
Just add the autoFocus tag to your input field, as FakeRainBrigand showed above.
Ref. #Dave's comment on #Dhiraj's answer; an alternative is to use the callback functionality of the ref attribute on the element being rendered (after a component first renders):
<input ref={ function(component){ React.findDOMNode(component).focus();} } />
More info
Using React Hooks / Functional components with Typescript, you can use the useRef hook with HTMLInputElement as the generic parameter of useRef:
import React, { useEffect, useRef } from 'react';
export default function MyComponent(): JSX.Element {
const inputReference = useRef<HTMLInputElement>(null);
useEffect(() => {
inputReference.current?.focus();
}, []);
return (
<div>
<input ref={inputReference} />
</div>
);
}
Or if using reactstrap, supply inputReference to innerRef instead of ref:
import React, { useEffect, useRef } from 'react';
import { Input } from 'reactstrap';
export default function MyComponent(): JSX.Element {
const inputReference = useRef<HTMLInputElement>(null);
useEffect(() => {
inputReference.current?.focus();
}, []);
return (
<div>
<Input innerRef={inputReference} />
</div>
);
}
Note that none of these answers worked for me with a material-ui TextField component. Per How to set focus to a materialUI TextField? I had to jump through some hoops to get this to work:
const focusUsernameInputField = input => {
if (input) {
setTimeout(() => {input.focus()}, 100);
}
};
return (
<TextField
hintText="Username"
floatingLabelText="Username"
ref={focusUsernameInputField}
/>
);
This is the proper way, how to autofocus. When you use callback instead of string as ref value, it is automatically called. You got your ref available than without the need of touching the DOM using getDOMNode
render: function() {
return <TextInput ref={(c) => this._input = c} />;
},
componentDidMount: function() {
this._input.focus();
},
You don't need getInputDOMNode?? in this case...
Just simply get the ref and focus() it when component gets mounted -- componentDidMount...
import React from 'react';
import { render } from 'react-dom';
class myApp extends React.Component {
componentDidMount() {
this.nameInput.focus();
}
render() {
return(
<div>
<input ref={input => { this.nameInput = input; }} />
</div>
);
}
}
ReactDOM.render(<myApp />, document.getElementById('root'));
You can put that method call inside the render function. Or inside the life cycle method, componentDidUpdate
I have same problem but I have some animation too, so my colleague suggest to use window.requestAnimationFrame
this is ref attribute of my element:
ref={(input) => {input && window.requestAnimationFrame(()=>{input.focus()})}}
AutoFocus worked best for me. I needed to change some text to an input with that text on double click so this is what I ended up with:
<input autoFocus onFocus={this.setCaretToEnd} value={this.state.editTodo.value} onDoubleClick={this.updateTodoItem} />
NOTE: To fix the issue where React places the caret at the beginning of the text use this method:
setCaretToEnd(event) {
var originalText = event.target.value;
event.target.value = '';
event.target.value = originalText;
}
Found here:
https://coderwall.com/p/0iz_zq/how-to-put-focus-at-the-end-of-an-input-with-react-js
<input type="text" autoFocus />
always try the simple and basic solution first, works for me.
To move focus to a newly created element, you can store the element's ID in the state and use it to set autoFocus. e.g.
export default class DefaultRolesPage extends React.Component {
addRole = ev => {
ev.preventDefault();
const roleKey = this.roleKey++;
this::updateState({
focus: {$set: roleKey},
formData: {
roles: {
$push: [{
id: null,
name: '',
permissions: new Set(),
key: roleKey,
}]
}
}
})
}
render() {
const {formData} = this.state;
return (
<GridForm onSubmit={this.submit}>
{formData.roles.map((role, idx) => (
<GridSection key={role.key}>
<GridRow>
<GridCol>
<label>Role</label>
<TextBox value={role.name} onChange={this.roleName(idx)} autoFocus={role.key === this.state.focus}/>
</GridCol>
</GridRow>
</GridSection>
))}
</GridForm>
)
}
}
This way none of the textboxes get focus on page load (like I want), but when you press the "Add" button to create a new record, then that new record gets focus.
Since autoFocus doesn't "run" again unless the component gets remounted, I don't have to bother unsetting this.state.focus (i.e. it won't keep stealing focus back as I update other states).
Simple solution without autofocus:
<input ref={ref => ref && ref.focus()}
onFocus={(e)=>e.currentTarget.setSelectionRange(e.currentTarget.value.length, e.currentTarget.value.length)}
/>
ref triggers focus, and that triggers onFocus to calculate the end and set the cursor accordingly.
Ben Carp solution in typescript
React 16.8 + Functional component - useFocus hook
export const useFocus = (): [React.MutableRefObject<HTMLInputElement>, VoidFunction] => {
const htmlElRef = React.useRef<HTMLInputElement>(null);
const setFocus = React.useCallback(() => {
if (htmlElRef.current) htmlElRef.current.focus();
}, [htmlElRef]);
return React.useMemo(() => [htmlElRef, setFocus], [htmlElRef, setFocus]);
};
Warning: ReactDOMComponent: Do not access .getDOMNode() of a DOM node; instead, use the node directly. This DOM node was rendered by App.
Should be
componentDidMount: function () {
this.refs.nameInput.focus();
}
The simplest answer is add the ref="some name" in the input text element and call the below function.
componentDidMount(){
this.refs.field_name.focus();
}
// here field_name is ref name.
<input type="text" ref="field_name" />
After trying a lot of options above with no success I've found that It was as I was disabling and then enabling the input which caused the focus to be lost.
I had a prop sendingAnswer which would disable the Input while I was polling the backend.
<Input
autoFocus={question}
placeholder={
gettingQuestion ? 'Loading...' : 'Type your answer here...'
}
value={answer}
onChange={event => dispatch(updateAnswer(event.target.value))}
type="text"
autocomplete="off"
name="answer"
// disabled={sendingAnswer} <-- Causing focus to be lost.
/>
Once I removed the disabled prop everything started working again.
Read almost all the answer but didnt see a getRenderedComponent().props.input
Set your text input refs
this.refs.username.getRenderedComponent().props.input.onChange('');
According to the updated syntax, you can use this.myRref.current.focus()
Focus using createRef for functional components
To developers using Functional Components. This seems to suit. Focus happens on inputfield after clicking on the button. I've attached CodeSandbox link too.
import React from 'react';
export default function App() {
const inputRef = React.createRef();
return <>
<input ref={inputRef} type={'text'} />
<button onClick={() => {if (inputRef.current) { inputRef.current.focus() }}} >
Click Here
</button>
</>
}
https://codesandbox.io/s/blazing-http-hfwp9t
That one worked for me:
<input autoFocus={true} />
Updated version you can check here
componentDidMount() {
// Focus to the input as html5 autofocus
this.inputRef.focus();
}
render() {
return <input type="text" ref={(input) => { this.inputRef = input }} />
})
Since there is a lot of reasons for this error I thought that I would also post the problem I was facing. For me, problem was that I rendered my inputs as content of another component.
export default ({ Content }) => {
return (
<div className="container-fluid main_container">
<div className="row">
<div className="col-sm-12 h-100">
<Content /> // I rendered my inputs here
</div>
</div>
</div>
);
}
This is the way I called the above component:
<Component Content={() => {
return (
<input type="text"/>
);
}} />

componentWillReceiveProps in Vue

I am new pretty new to Vue, and coming from a rather React-y suburb. I am rebuilding my SideNav ("drawer") component from the latter. There, when one clicked the button (not being related to the navigation per se), it setStateed this.state.toggle that was tied to appropriate
class thePage extends React.Component {
...
this.handleToggleClick = this.handleToggleClick.bind(this);
this.state ={
toggleState: false
};
}
// Slide out buttons event handlers
handleToggleClick(){
this.setState({
toggleState: !this.state.toggleState
})
}
render() {
const button = <a href="#" onClick={this.handleToggleClick}>here</a>
const isOpenWithButton = this.state.toggleState;
return (
<div>
{button}
<SideNav logo="logo.png" isOpenWithButton={isOpenWithButton}>
. . .
</SideNav>
</div>
);
}
}
export default SideNavPage;
the SideNav looks as follows:
class SideNav extends React.Component {
constructor(props){
super(props);
this.state = {
isThere: false,
showOverlay: false,
}
this.handleOverlayClick = this.handleOverlayClick.bind(this);
}
componentWillReceiveProps(NextProps) {
if (this.props.isOpenWithButton !== NextProps.isOpenWithButton) {
this.setState({
isThere: true,
showOverlay: true
})
}
}
handleOverlayClick(){
this.setState({
isThere: false,
showOverlay: false
});
}
render() {
const {
tag: Tag,
...
isOpenWithButton,
} = this.props;
let isThere = this.state.isThere;
let showOverlay = this.state.showOverlay;
const overlay = <div class="overlay" onClick={this.handleOverlayClick}></div>
const sidenav = (
<Tag>
<ul>
{logo &&
<li>
<div className="logo-wrapper">
<a href={href}>
<img src={logo} className="img-fluid flex-center d-block"/>
</a>
</div>
</li>
}
{children}
</ul>
</Tag>
);
return (
<div>
{isThere && sidenav}
{showOverlay && overlay}
</div>
);
}
}
export default SideNav;
So, as you can see, clicking the button causes the isOpenWithButton props to change, and whenever it happens (componentWillReceiveProps), the sidenav with overlay appear.
I did some work on porting it to Vue, but as it lacks this lifecycle hook I am stuck with props. I have a following problem: clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change, what necessitates clicking the button twice if the sidenav has been already open. I know I must be missing a vital part in Vue logic, I just cannot grasp which.
Using .sync modifier
What you are looking for is called in vue a .sync modifier.
When a child component mutates a prop that has .sync, the value change will be reflected in the parent.
With this you can achive what you described:
clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change
Using a centralised store - (like vuex)
The same could also be achieved if you have a centralised state/store, in this case both of your components could rely on that state property.
See state management on Vue documentation:
Large applications can often grow in complexity, due to multiple pieces of state scattered across many components and the interactions between them
You could simple toogle the same property, for example:
$store.commit('overlayToggle');

Delay in Rendering a Component?

I'm attempting to make my own personal website, and trying to use React to do so. In the process, I intend to make each section a different React Component. My plan is to have the navbar at the top be able to select which component is currently "active", and actually gets rendered and shown. In addition, when switching to a new section, I would like the old component to have a "leaving" animation, and the new component to have an "entering" animation (these are done with react-motion). However, currently both the entering and leaving are done at the same time, because I'm changing the active state for both components at the same time. Is there any way to delay one component becomes active after another one becoming inactive?
The parent component that houses each section looks like so:
class Website extends React.Component {
constructor(props){
super(props)
this.state = {
homeActive: true,
aboutActive: false
}
homeActivator(){
this.setState({
homeActive: true,
aboutActive: false
})
}
aboutActivator(){
this.setState({
homeActive: false,
aboutActive: true
})
}
render(){
return (
<div>
<NavBar handleHome={this.homeActivator.bind(this)} handleAbout=
{this.aboutActivator.bind(this)}/>
<Home active={this.state.homeActive} />
<About active={this.state.aboutActive} />
</div>
}
And then one of the "sections" would look like so:
class Home extends React.Component{
render() {
let content = (
<div>
Home
</div>
)
if (!this.props.active){
return (
//Some jsx that results in the content leaving the page
)
}
return(
//Some jsx that results in the content entering the page
)
}
}
I did not have a ton of time to answer this, but came up with the best example I could. It's not an exact replica of what you are looking to do, but is very similar, so if you understand it, you will be able to figure out your problem quite easily.
To make things a little easier to understand, I am mimicking components with methods placed inside the React Class. Obviously in the real world, you would be importing your components from other files. I'm sure you'll understand what's going on.
export default class Example extends Component {
constructor(props) {
super(props)
this.state = {
c1: true,
c2: false
}
}
// Component One
renderc1() {
return (
<div>
I am component one
</div>
)
}
// Component Two
renderc2() {
return (
<div>
I am component two
</div>
)
}
changeComponents = () => {
this.setState({ c1: false })
setTimeout(() => {
this.setState({ c2: true })
}, 1500)
}
render() {
return (
<div className="example">
{this.state.c1 ? this.renderc1() : null}
{this.state.c2 ? this.renderc2() : null}
<button onClick={this.changeComponents}>Click me</button>
</div>
)
}
}
Clicking the button will fire off the changeComponents function, which will then immediately set the state of "c1" to false. A setTimeout after that ensures that component 2 will be delayed rendering to the screen.
Notice the arrow syntax, I used, which binds the this keyword to the class, so you don't have to worry about writing bind this everywhere.

ReactJS keeping a single "Active" state between multiple components

I am attempting to keep with best practices, while adhering to the documentation. Without creating to many one-off methods to handle things for a maintainability standpoint.
Anyway all in all, I am trying to achieve a state between sibling elements that is in sorts an "active" state visually at the least. With something like jQuery I would simply do..
$(document).on('.nav-component', 'click', function(e) {
$('.nav-component').removeClass('active');
$(this).addClass('active');
});
However in react, each component in it of itself is independent of the next and previous, and should remain as such per the documents.
That said, when I am handling a click event for a component I can successfully give it a state of active and inactive, toggling it on and off respectively. But I end up in a place where I have multiple "active" elements when I don't need them as such.
This is for setting up a navigation of sorts. So I want the one in use at the moment to have that active class while the rest won't
I use an app.store with reflux to set state for multiple pages/components. You can do the same passing state up to a common component but using the flux pattern is cleaner.
class AppCtrlRender extends Component {
render() {
let page = this.state.appState.currentPage;
let hideAbout = (page != 'about');
let hideHome = (page != 'home');
return (
<div id='AppCtrlSty' style={AppCtrlSty}>
<div id='allPageSty' style={allPageSty}>
<AboutPage hide={hideAbout} />
<HomePage hide={hideHome} />
</div>
</div>
);
}
}
let getState = function() { return {appState: AppStore.getAppState(),}; };
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getState();
}
componentDidMount = () => { this.unsubscribe = AppStore.listen(this.storeDidChange); }
componentWillUnmount = () => { this.unsubscribe(); }
storeDidChange = () => { this.setState(getState()); }
}
In the page/component check for this.props.hide.
export default class AboutPage extends Component {
render() {
if (this.props.hide) return null;
return (
<div style={AboutPageSty}>
React 1.4 ReFlux used for app state. This is the About Page.
<NavMenu />
</div>
);
}
}
Siblings needing to share some sort of state in React is usually a clue that you need to pull state further up the component hierarchy and have a common parent manage it (or pull it out into a state management solution such as Redux).
For sibling components where only one can be active at a time, the key piece of state you need is something which lets you identify which one is currently active and either:
pass that state to each component as a prop (so the component itself can check if it's currently active - e.g. if each item has an associated id, store the id of the currently active one in a parent component and pass it to each of them as an activeId prop)
e.g.:
var Nav1 = React.createClass({
getInitialState() {
return {activeId: null}
},
handleChange(activeId) {
this.setState({activeId})
},
render() {
return <div className="Nav">
{this.props.items.map(item =>
<NavItem
activeId={this.state.activeId}
item={item}
onClick={this.handleChange}
/>
)}
</div>
}
})
or use it to derive a new prop which is passed to each component (such as an active prop to tell each component whether or not it's currently active - e.g. in the id example above, check the id of each component while rendering it: active={activeId === someObj.id})
e.g.:
var Nav2 = React.createClass({
// ... rest as per Nav1...
render() {
return <div className="Nav">
{this.props.items.map(item =>
<NavItem
active={this.state.activeId === item.id}
item={item}
onClick={this.handleChange}
/>
)}
</div>
}
})
The trick with React is to think of your UI in terms of the state you need to render if from scratch (as if you were rendering on the server), instead of thinking in terms of individual DOM changes needed to make the UI reflect state changes (as in your jQuery example), as React handles making those individual DOM changes for you based on complete renderings from two different states.

Categories

Resources