Getting Child Component Prop from the Parent Component - javascript

I'm using following React Native module:
https://github.com/shariqahmed1/react-native-animated-tree-view
I like to get clicked item from a TreeView.
Documentation says I should be able to get clicked item by onClick prop.
My attempt was like that:
<TreeView
onPress={() => console.log(props.onClick)} //Cannot get clicked item
data={data} //Works Okay
/>
I'm able to give source data successfully but I can not get the clicked item from tree view.
How can I get the child component value from parent component?

You can use Ref/createRef to give it a unique reference (just like ID) and then you have the access to it:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// create a ref to store the textInput DOM element
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}
render() {
// tell React that we want to associate the <input> ref
// with the `textInput` that we created in the constructor
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
In the onclick event handler, use this.onClick() instead of props.onClick() and in this.onClick, you can access child component using this.textInput.current to access the child component.

You should use onClick inplace of onPress as per the doc to get clicked item:
onClick={props.onClick} // <-- will call parent component's onClick
// OR
onClick={(item) => props.onClick(item)} // <-- will call parent component's onClick
To check console, try below code :
onClick={(item) => { console.log(item); }}

Related

React - parent class component function change class of child component

I have two components, a parent component and its children components.
In the parent component I store a state for 'active' which holds the id of the active child component. What I'd like to do is have a handleClick function that compares the id of the child component which is being clicked to the value of 'active' and if it's the same, or different, i'd like it to update html className of the child component to achieve a certain style effect (which may include some animation).
Questions:
Is there a way to target the className of a particular child element and update it?
Is it instead better to handle this function in the child itself while storing the id of the 'active' child in the state of the parent component?
If i'm looking to achieve css animations based on the change in className of the child component from one className to another, are there additional considerations, such as including that the animation run on render of the component, if i'm hoping to animate the change this way?
I'm sure there are other ways to approach this and i'm totally open to suggestions on the best approach to achieve the above, but I'll also include that I am just getting started with react and haven't learned about how to use hooks yet. I'm still working with basic functional and class based components.
Thanks in advance and example code with pseudo code below.
example parent component:
import React, {Component} from "react";
import Task from './openTasks';
import TasksData from './tasks-data';
class openTaskAccordion extends Component{
constructor(){
super()
this.state = {
//holds the id of the currently opened/active item but initialized to -1 since there is no item with an id of -1 at initialization.
active: -1
}
this.handleClick() = this.handleClick.bind(this);
}
handleClick(){
//if (the id of the clicked task === this.state.active){
// change the className of the clicked child component to "closed"
// } else {
// change the className of the child component with id == this.state.active to "closed", change the className of the clicked child component to "open" and update this.state.active to the id of the now open child component with setState.
//
}
render(){
const Tasks = TasksData.map(task=> <Task key={task.id} task ={task}/>)
return(
Tasks
)
}
}
export default openTaskAccordion
example child component
import React from "react";
import "./OpenTasks.css"
function openTasks(){
return (
<div id = {props.task.id} className="tasks" value = {props.task.id}>
<h1 >{props.task.clientName}</h1>
<div className="accordion-item accordion-item-closed" >
<h2>{props.task.matter}</h2>
<p> - {props.task.matterStatus}</p>
</div>
</div>
);
}
export default openTasks
Issues
Parent component
this isn't bound correctly in constructor for handleClick.
Child component.
openTasks is a functional component, so there is no this, or rather, this will just be undefined.
openTasks doesn't consume any of the props passed to it.
To answer questions
Is there a way to target the className of a particular child element and update it?
You could do this, but direct DOM manipulations and reaching into other components to change things is anti-pattern in React. The react way is to pass data as props (data including what is or isn't "active") and letting children components handle it locally.
Is it instead better to handle this function in the child itself while
storing the id of the 'active' child in the state of the parent
component?
No, I don't think so, the parent should store the single source of truth about the current "active" child. Pass props to the child, including any callbacks the child could/should call to update state in the parent.
Solution
The parent component should store the active child, as you've done, but you should pass the active id and a callback to the children in order for them to be "clickable" and allow the parent to update what the active child is.
OpenTaskAccordion
Fix the this binding in the constructor
Update handleClick to consume a task id to toggle active state of
Pass active state and onClick callback to Task
code
class OpenTaskAccordion extends Component {
constructor() {
super();
this.state = {
active: -1
};
this.handleClick = this.handleClick.bind(this); // <-- fix this binding
}
handleClick(id) { // <-- consume task id
this.setState((prevState) => ({
...prevState,
active: id === prevState.active ? -1 : id
}));
}
render() {
const { active } = this.state;
const { tasks = [] } = this.props;
return tasks.map((task) => (
<OpenTask
key={task.id}
active={active}
task={task}
onClick={this.handleClick}
/>
));
}
}
OpenTasks
Remove all the this references since this is a functional component.
Consume props object.
Add an "active" class if props.active matches the current task id.
Attach props.onClick to something clickable to toggle the active state in parent.
code
function OpenTask(props) { // <-- consume `props`!!
return (
<div
id={props.task.id}
className={["tasks", props.active === props.task.id && "active"].join(" ")}
value={props.task.id}
onClick={() => props.onClick(props.task.id)} // <-- attach onClick callback
>
<h1>{props.task.clientName}</h1>
<div className="accordion-item accordion-item-closed">
<h2>{props.task.matter}</h2>
<p> - {props.task.matterStatus}</p>
</div>
</div>
);
}
CSS used to apply an "animation" for active item toggling. Uses a simple CSS transition on the background color.
.tasks {
transition: background-color ease-in-out 0.5s;
}
.active {
background-color: lightblue;
}
Comment Questions
In the functional child component, where can I read about what this
is doing? className={["tasks", props.active === props.task.id && "active"].join(" ")}
It is simply a way to create a list of space-separated class names, i.e. "tasks" or "tasks active" from ["tasks"] or ["tasks", "active"].
Some alternative methods include
className={`tasks ${props.active === active ? "active" : ""}`}
className={"tasks" + `${props.active === active ? " active" : ""}`}
In the parent class component, what is this doing and why isn't
{tasks =[] } overridden by the data set you created in your
example?
const { active } = this.state;
const { tasks = [] } = this.props;
const { tasks = [] } = this.props; is just a way to provide a defined value for the mapping in the case that this.props.tasks is undefined (or falsey), as would be the case if a tasks prop was not passed to the component. So long as this.prop.tasks is a defined truth value then that is what is used. Consider this a fallback value.

How to Focus on nested Element by passing reference to the Parent Component in React?

I have a parent Component which sends a list of data to a List component which in turn sends it to a Label Component to display each data as a label.
I want to be able to focus on the label element when i click on it so that the appropriate style is applied ..
Below is the gist :-
class ContainerComp extends React.Component {
constructor(props) {
super(props);
this.state = {
group: [1, 2, 3]
};
clickHandler = (name, ref) = > {
// I am able to get the DIV as a html element here but calling .focus() on it dosent change the style where as when i explictly add focus using chrome debugger for the element it works.
ref.focus() // not working
}
render() {
return ( <
ListComp group = {
group
}
onClick = {
clickHandler
} >
)
}
}
function ListComp(props) {
const data = props.group.map(... < label onClick = {} > )
return ( <
Label.. >
)
}
function Label(props) {
let ref = createref();
// on focus style for the component is defined in this component
// i am making use of css modules
return ( <
div ref = {
ref
}
onClick = (name, ref) >
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
How can we achieve such a functionality without having to pass a selected prop to the label component ? By default i would select the first element and keep the focus .. or we can make it configurable.
Usually for this I would use Redux and fire off an action which therefore sets the property of the component that needs change, and make a listener that will listen for that specific prop and change style accordingly.
In this situation, id just pass down the event handler to the child component (remember to not call it when you pass it down, so do:
{() => {eventHandler()}}
and then in the child component do:
onClick={this.props.eventHandler(e)}
You will use the event to identify which element triggered it and then apply the class/style/prop to it.
There was some problem with the Ref , I am not quite sure why but i changed it to use the useRef() hook.
Label Component
const elementRef = useRef(null);
return (
<div className={[externalStyle, styles.container].join(' ')} onClick={() => onClickEvent(itemName, elementRef)} ref = {elementRef} tabIndex={1}> // added tabIndex and also changed to useRef
Container Component
clickHandler = (name: string, ref) => {
ref.current.focus(); // volla it worked
}
I tried using the old form of Ref and also useRef() without null previously (el) => (const = el).
It works if some one has some explanation where i went wrong i will be happy to listen as i am able to wrap my head around. may be a nights sleep helped fix it :P

Binding setState() to a global function only affects one instance of a component

Note: I have edited the question after the changes I have made according to Nicholas Tower's answer.
I have a global function which bound to a component and changes it's state.
I want to build a form builder system. There is a global function named setLabel which is bound to a component named InputBox and changes it's state. This global function is triggered via another component named ModalPanel which controls the editable properties on the bound component InputBox. I have simplified the function and component class for simplicity of this question.
Here is the global function:
function setLabel(postID, attributeName ){
var text = 'example';
if(text !== ''){
this.setState({ [attributeName] : text});
}
}
And here is the component which is bound to the setLabel function. Notice how setLabel function is passed from parent InputBox component to child ModalPanel component function as a property.
class InputBox extends Component{
constructor(props){
super(props);
this.state = {
placeholder : '',
maxlength: '',
type: '',
}
this.setLabel = setLabel.bind(this); // Binding this to the global function.
}
render(){
let elementId = "inputElement-" + this.props.idCounter;
let mainElement = <Form.Control
id = {elementId}
type = {this.state.type}
placeholder = {this.state.placeholder}
maxLength = {this.state.maxlength}
/>
return <Row>
<ModalPanel
handleHide = {this.props.handleHide}
handleShow = {this.props.handleShow}
setLabel = {this.setLabel}
/>
</Row>
}
}
Lastly, below is the ModalPanel component function where the setLabel function is triggered.
function ModalPanel(props){
return(
......................
......................
......................
<Button variant="primary" onClick = {() => props.setLabel()}>Save changes</Button>
......................
......................
......................)
}
setLabel function which is aimed to set the state of InputBox must be triggered when a button is clicked in the ModalPanel component. The problem is, there are multiple rendered <InputBox /> components on the window and when I try to use this functionality, "the state change" only affect the first instance of <InputBox /> component. What I want to do is that, every instance should have their own internal state and setLabel() function should be bound to the specific component from where it is called. So that, this function can be able to set the state of different component instances. How could I do that?
Addition:
Please check the link below to see a gif image showing how my system works wrong. As you can see, even though I choose the third input box element to edit it's properties (in this case, set it's placeholder text), the change is being made to the first one.
Go to gif
Add a this. to the beginning, as in:
this.setLabel = setLabel.bind(this);
Now you're setting a property on the instance of the InputBox. Make sure to refer to it using this.setLabel when you reference it later in the component.
Is setLabel acting on a specific postID? Is the problem that <Button /> of every <ModalPanel /> acting on the same postID? Because you aren't using setLabel correctly inside <ModalPanel />. setLabel takes in 2 arguments and right now your implementation isn't using any. This is your click handler.
onClick = {() => props.setLabel()}
Try console.logging inside setLabel and see what values you're getting when you click on each button
function setLabel(postID, attributeName){
console.log(postID, attributeName)
var text = 'example';
if(text !== ''){
this.setState({ [attributeName] : text});
}
}
Since the React components only updated from props or state changes, you need to pair the global state with a local state to update the component. See the code below in a sandbox environment.
let value = 0;
function updateStuff () {
console.log("this from update", this.name);
value++;
this.setState({name: "Hakan " + value});
}
class Test extends React.Component {
constructor(props){
super(props);
this.state = {
name: 'notchanged',
counter: 1
}
this.localFunc = this.localFunc.bind(this)
updateStuff = updateStuff.bind(this)
}
localFunc(){
let {counter} = this.state;
this.setState({counter: counter + 1});
updateStuff();
}
render () {
return (
<div>
<div>Test 2</div>;
<div>Counter: {this.state.counter}</div>
<div>Name: {this.state.name}</div>
<button onClick={this.localFunc}>Increment</button>
</div>
);
}
}
ReactDOM.render(
<Test/>,
document.getElementById('root')
);
Think, you are using React in incorrect way
The preferred way for me looks like:
Have a dumb/presentational InputBox which accepts label as a property (in props, not in state)
Have a smart/container component which contains state of multiple InputBoxes and passes the correct label into InputBox
If you are trying to implement InputBox PropertyEditor as a separate component - consider adding event bus, shared between them for example via React Context (or even use full flux/redux concept)
Add this to your function calls after binding them or use arrow functions !

How to select the clickable parent in my React component even if I click on its children or anywhere else inside the parent

I have started an application which I want to work same as weather.com next 36 hours section. The idea is when you click on each weatherCard which has a seperate component in my app you will update the below section which is my weatherDetails component based on the selected weatherCard /weather box. So I made the entire component clickable by giving it the click event via props from my stateful component which is my weatherLocation component. This is my WeatherCard component:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={props.clicked}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
And here in render method in WeatherLocation component I loop through data coming from state and give props the WeatherCard component:
const WeatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
{...report}
clicked={() => this.handleCardClick(event)}
/>
);
});
And this is the handleCardClick that I added for it just for testing:
handleCardClick = event => {
// const { reports , selectedCardInfo , activeCard } = this.state;
const selectedDate = document.getElementById(event.target.id);
console.log(event.target.id);
}
I don't want to use anchor tag as I don't need href. The click works fine by itself. But because I need to get the id of the parent which is the div with the class of weatherCard. At the moment when I click on other elements inside the card I cannot get the id because they are not the parent. The reason I need its id is when I get data with from the API I need a unique value for each card so that when you click on the card the data for that card will be shown in the other component which is the WeatherDetails component. But for now I need to be able to somehow choose that selected card and pull out the state for that unique card. Could someone help me out? Thanks.
You just need to pass the Parent component ID to your onClick function in Weather Card.
Here is your WeatherCard - Component
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
You can see that I have added props.id to your onClick function and with help of event now you can access that id from the parent component.
Now here is your Parent Component- WeatherCards
const WeatherCards = this.state.reports.map( (report, i) => {
return(
<WeatherCard
key={report.id}
id={i}
{...report}
clicked={this.handleCardClick}
/>
);
});
You can see in the code I am passing index number as id to your child component.
So this will give you an id (for now it's an index number) of the card in your onClick handler.
and Finally, here is your on click handler.
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
As of now, I am using the index as id if you want to use a unique identifier, you can change that easily.
General JavaScript solution is to differentiate the elements and .stopPropogation after you've captured the event you are targeting. A nested unordered list, <ul>would be an example. Tag the containing <li> with an .opened class upon rendering/displaying each level of nesting, tag those <li> elements accordingly, e.g. a dataset attribute such as data-make, then data-model, then data-option. You then attach and fire event listeners on the different level <li>'s.
Thank you #RutulPatel. I made your answer as the answer. But I changed your code a bit as I got your point so I wrote an answer as it is long. I think we might not need to change the WeatherCard at all and I don't pass event or any logic there. so it will be intact:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
But I use your tip changing my weatherCards array to look like this:
const weatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
id={report.date}
{...report}
clicked={() => this.handleCardClick(event, report.date)}
/>
);
});
So I use the report.date which is a unique value as my id. Also I don't pass event as a parameter to the arrow function I just pass it with the report.date to the handler:
clicked={() => this.handleCardClick(event, report.date)}
And the handler will be the same as you did:
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
I might even remove event later on from both if there was no need fo that.
Thank you again.

How to make the input not to lose focus when using state and Function components in ReactJS?

I use a Function component to render a component.
But the input control loses focus whenever the state is set.
How to change the below code so that the input doesnt' lose focus? I'd need the component to be a Function component.
There are 10 inputs on my form so depending on which text box has changed, it should place the focus on the changed text box after being rendered.
in the changeHandler(event), I know the event which contains the target property which refers to the changed text box so somehow I'd need to set the focus to the target input element after the state being rendered.
maybe I should define a class level variable and set the target to that.
import React, {Component} from 'react';
export default class PlaybackPassword extends Component{
constructor(props) {
super(props);
this.state = {
setting: {}
}
this.focusElement = null;
this.changeHandler= this.changeHandler.bind(this);
}
componentDidUpdate()
{
if (this.focusElement != null)
this.focusElement.focus();
}
changeHandler(event)
{
this.focusElement = event.target;
var setting = this.state.setting;
setting.SelectedValue = event.target.value;
this.setState({setting : setting});
}
render() {
var parent = this;
function Password(props)
{
return (<div className="password">
<input type="password"
value={props.selectedValue} onChange={parent.changeHandler} />
</div>);
}
return (
<div className="playbackPassword">
<Password selectedValue={this.state.setting.SelectedValue} />
</div>
);
}
}
In the example code, you are defining (i.e., creating) a new function Password on every render. This means that your <Password> component will be a brand new component every time.
The key is to define your function once, outside of the render function, so that React can track that the <Password> component is in fact the same component between renders.
Example: https://codesandbox.io/s/nn2nwq2lzp
Note: you won't be able to use parent but will need to pass a prop instead.

Categories