I'm new ot react native and am having a hard time with the idea of not using inheritance, but rather composition.
My scenario: I'm creating a component (focusedTextInput) which shows one InputText. My component simply adds functionality to change the style of the TextInput on focus.
I'm using FocusedTextInput in another component which contains five focusedTextInput and I configure those to only have one character at a time and to auto-skip to the next FocusedTextInput when the character is entered (using the focus() method).
Where I'm running into issues is the my FocusedTextInput does not have a focus method (and I don't want to expose the TextInput).
So do I need to surface all the method that might be used from TextInput on FocusedTextInput or is there a better way?
See this answer React set focus on input after render on stack overflow.
I think this applies to react-native but it does work in the web.
That shows you how to set a ref to a component. And in the CDM set the focus to that component.
To extend how that works so you can set the focus to a specific input (if there are many) is to add a prop called setFocused
Change the CDM to
// Set the focus on first render.
componentDidMount(){
if (this.props.setFocus) {
this.nameInput.focus();
}
}
// focus if the prop changes
componentWillRecieveProps(nextProps) {
if (this.nextProps.setFocus) {
this.nameInput.focus();
}
}
Related
Say I am building an instant messaging with app with React (I'm not doing that exactly, but this is easier to explain). I have a sidebar with a list of conversations and, when you click one, it is shown on the right (similar to this). I don't want to mount each conversation component until the user clicks it, but I don't want to unmount it, just hide it, when they click on another conversation. How can I do this cleanly? There will never be more than about 30 chats for any user.
You can store the enabled conversations in an array that you use to show, and when you disable a conversation you can just add a hidden prop to it which you pass to the conversation and make it return null. This will make it not render anything but will not unmount it since you have not removed it from the array that handles the display of conversations.
example at: https://codesandbox.io/s/wispy-forest-59bqj
This is a bit hard to answer since you haven't posted the code.
But, theoretically, the best way to approach this problem is to transfer the data from your sidebar component and load it onto the right component on a per-user basis. You don't have to mount each "conversation component".
You can do this by with the boolean hidden property in your markup. React will render as usual and simply pass it along to the html, the browser will then simply not paint it.
const HideMe = ({ isHidden }) => (
<div hidden={isHidden}>
can you see me?
</div>
)
I made an example for you:
https://codesandbox.io/s/elastic-curie-t4ill?file=/src/App.js
reference: https://www.w3schools.com/tags/att_hidden.asp
I have a React application using Material UI with a component (which we can call DatePicker) shown below, sneakily changed for demo purposes.
Material UI animates clicks and other interactions with its components. When clicking a radio button that has already been selected, or a "time button" which doesn't change state, this animation is visible above. However, when such a click changes the state, the animation get interrupted.
I can see why this happens from a technical perspective; the DatePicker component calls setMinutes, which is a property passed in from its parent (where the state lives). This is a React.useState variable, which then updates its corresponding minutes variable. Minutes is then passed into DatePicker, which re-renders due to a prop change.
If state lived within DatePicker then this problem shouldn't rear its head; however, DatePicker is one part of a much larger form which dictates the contents of a table in the parent. To generate rows for this table, the parent must have this information.
Below is a sample reconstruction of the parent:
const Parent = () => {
const [minutes, setMinutes] = React.useState(15);
const [radioOption, setRadioOption] = React.useState('Thank You');
// Many other state variables here to hold other filter information
return (<div>
<DatePicker minutes={minutes} setMinutes={setMinutes} radioOption={radioOption} setRadioOption={setRadioOption}/>
</div>);
};
And here a sample reconstruction of DatePicker:
const DatePicker: React.FC<DatePickerProps> = props => {
const {minutes, setMinutes, radioOption, setRadioOption} = props;
return (<div>
<Radios value={radioOption} onChange={val => setRadioOption(val)}/>
<Minutes value={minutes} onChange{val => setMinutes(val)}/>
</div>);
};
I'm not sure what the best practice is in this situation, but I get the distinct feeling that this is not it. Does anyone have any advice? Thanks in advance!
Thank you for your comment, Ryan Cogswell. I did create a code sandbox, and found that the problem was not about React state management as much as what I was doing beyond what I provided in my question.
I was using the withStyles HOC to wrap my component, in a way similar to const StyledDatePicker = withStyles(styles)(DatePicker). I then used that styled element and put properties (minutes, etc) on that.
It turns out that using the unstyled DatePicker resolves this issue. I troubleshooted this further, and found that I had created the "Styled" component within the "render" method of the parent, meaning every time a prop change was pushed up the chain, the parent would re-render and the entire "Styled" component type would be created again (or so I believe). This would break reference integrity, which explains the "drop and recreate" behaviour.
This teaches the valuable lesson of keeping components small and using code sandboxes for troubleshooting. Thanks again!
For anyone interested, here is the Code Sandbox used for testing.
Backstory
I want to include a BokehJS plot in my React component. The process for this is to render <div id="my_plot_id" className="bk-root"/> and call window.Bokeh.embed.embed_item(plotData, 'my_plot_id') which injects needed HTML into the DOM.
Because I want to control the BokehJS plot using the React component's state (i.e replace the plot with new generated plot data), I don't want to just call embed_item() in componentDidMount(). I've instead placed embed_item() in render() and added some code to remove child nodes of the container div prior to this call.
Problem
My React component renders 3 times on page load and although by the final render I have only one plot displayed, there is a brief moment (I think between the 2nd and 3rd/final render) where I see two plots.
Code
render()
{
let plotNode = document.getElementById('my_plot_id');
console.log(plotNode && plotNode.childElementCount);
while (plotNode && plotNode.firstChild) {
//remove any children
plotNode.removeChild(plotNode.firstChild);
}
const { plotData } = this.state;
window.Bokeh.embed.embed_item(plotData, 'my_plot_id');
return(
<div id="my_plot_id" className="bk-root"/>
)
}
In console I see:
null
0
2
Question
So it seems embed_item executes twice before the my_plot_id children are correctly detected.
Why is this happening and how can I resolve it? While the triple render may not be performance optimized I believe my component should be able to re-render as often as it needs to (within reason) without visual glitching like this, so I haven't focused my thought on ways to prevent re-rendering.
Interaction with DOM elements should never happen inside the render method. You should initiate the library on the element using the lifecycle method componentDidMount, update it based on props using componentDidUpdate and destroy it using componentWillUnmount.
The official React documentation has an example using jQuery which shows you the gist of how to handle other dom libraries.
At start plotNode is unable to reach 'my_plot_id'.
You can render null at start, when plotData is unavailable.
You can use componentDidUpdate().
In this case I would try shouldComponentUpdate() - update DOM node and return false to avoid rerendering.
I am creating a profile screen for user in my apps . I am using lightbox from React-Native-Navigation by wix to perform an edit profile . So , the user will click the touchableopacity and a lightbox will pop up and the user will enter the new information and save it . So, im wonder is it possible if i want to pass the textinput value from lightbox to the parent(profile.js) so that i can setstate in the profile.js ?
Yes this is possible. You will need to send the data as props to the parent. If you haven't done it before it might feel a bit tricky but you'll get there.
From the parent:
<LightboxComponent
userData={this.handleUserData(data)}
/>
handleUserData(data) {
/* Do something with the data here */
}
From the child:
To send the data you need to set an onChange event or similar on the input you want to capture, like this:
<input name="user-name" onChange={ (e) => this.props.userData(e.target.value) }
This will make the input data from the child get sent to the parent. Every change will trigger a re-render of the affected components.
If your app complains about not being able to setState correctly, then you need to bind this in the parents constructor like this:
this.handleUserData = this.handleUserData.bind(this);
I would also say pass the parent's function pointer to the child as props (as seen on the React site). Although some people opt for using an event emitter. I'm actually really curious about more developers' opinions on that.
Is derailing a thread on StackOverflow grounds for epic down voting?
I'm running into a weird case that only seems to happen upon first loading a component on a heavily based component page (loading 30+ components).
#Component{
selector: <parent-component>
template: `<child-component [myObject]=myObject>
}
export class ParentComponent {
private myObject:DTOValue;
constructor(service:MyService){
service.getDTOValue().subscribe((dtoValue:DTOValue) => {
this.myObject = dtoValue;
});
}
}
#Component{
selector: <child-component>
template: `<div></div>
}
export class ChildComponent {
#Input set myObject(value:DTOValue) => {//do something};
constructor(){
}
}
In this code, the Parent is going to get a value to a child as an input. This value comes from a request at a later time, so when the child is first initialized, the input could be undefined. When the value does get returned from the request and is set on the variable myObject, I'd expect that the child component would receive an input event being triggered. However, due to the timing, it seems like this is not always the case, especially when I first load a page that contains a lot of files being loaded.
In the case that the child component doesn't receive the input, if I click else where on my page, it seems to now trigger the change detection and will get the input value.
The 2 possible solutions I can think of that would require some large code changes so I want to make sure I choose the right now before implement them.
Change the input to be an Subject, so that I push the input value which should ensure that a correct event is triggered(this seems like overkill).
Use the dynamic loader to load the component when the request as return with the correct value (also seems like overkill).
UPDATE:
Adding a plnker: http://plnkr.co/edit/1bUelmPFjwPDjUBDC4vb, you can see in here that the title seems to never get its data bindings applied.
Any suggestions would be appreciated.
Thanks!
If you can identify where the problem is and appropriate lifecycle hook where you could solve it, you can let Angular know using ChangeDetectorRef.
constructor(private _ref: ChangeDetectorRef)
method_where_changes_are_overlooked() {
do_something();
// tell angular to force change detection
this._ref.markForCheck();
}
I had a similar issue, only with router - it needed to do redirect when/if API server goes offline. I solved it by marking routerOnActivate() for check...
When you trigger change detection this way a "branch" of a component tree is marked for change detection, from this component to the app root. You can watch this talk by Victor Savkin about this subject...
Apologize, the issue ended up being my interaction with jQuery. When I triggered an event for a component to be loaded, inside of the jQuery code, it wouldn't trigger the life cycle. The fix was after the code was loaded to then call for a change detection.