So the user makes changes to the state through an input element with this function:
handleCardChange = (event, style) => {
let updated = event.target.value;
this.setState(previousState => ({
styling: {
...previousState.styling,
styling: {
...previousState.styling.styling,
card: {
...previousState.styling.styling.card,
height: updated
}
}
}
}))
}
This updates the state fine, for example if the user puts "600px" in the field then the state will be 600px. Since the state has changed, I want to trigger a re-render of my child component since it uses its props.
I used a componentWillReceiveProps() for this like so:
componentWillReceiveProps(nextProps) {
console.log("componentWillRecieveProps: ", this.props.styling.styling.card.height);
this.state = {
styling: this.props.styling.styling
}
}
The problem I'm running into is that the child is behind the parent by one character at all times.
So if parents height is 600px, the childs value is 600p and I need to add another character in the input for the child to update.
Is there any way to fix this and make sure the child component will keep up to date with the parents current state?
Related
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.
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
I've created a React component which takes any component and renders it as a Pop-up. A parent component receives the component to be rendered (popped up). The rendered component is here the child component which using react-sizeme to get its size and pass back to parent component. The parent component must take the dimensions of child component, so adjusts' its height and width. This is the code:
class Popup extends React.Component<IPopupProps,IComponent>{
constructor(props:IPopupProps){
super(props);
this.state={
childComponent:this.props.children,
style:{
height:0,
width:0
}
}
}
// This function runs two times, before and after rendering child component
// & so have an improper visualization as the size is changed twice here
public OnSize = (size:any) =>{
const width = size.width +20;
const height =size.height+20;
this.setState({
style:{height,
width }
})
}
public render(){
return(
<div className='popup'>
<div style={this.state.style} className='popup-content'>
<a className="close" onClick={this.props.onExit}>
×
</a>
<this.state.childComponent onSize={this.OnSize}/>
</div>
</div>
)
}
}
The initial width and height is set to 0. So it doesn't renders properly. So is there any way so that to hide the child component or avoid its rendering before parent component gets the size?
EDIT: We can't get the size until the child component is rendered. So is there any trick to get this done. Just a component needs to be popped-up properly.
EDIT 2: Here's the PropsBuilder.tsx which calls the Popup.tsx and sends the component to display as children
class PopupBuilder extends React.Component<IPopupBuilderProps, IPopup>{
constructor(props:IPopupBuilderProps){
super(props);
this.state = {
showPopup:false
}
}
public togglePopup = () =>{
this.setState({
showPopup:!this.state.showPopup
})
}
public render (){
return(
<React.Fragment>
<button onClick={this.togglePopup}>{this.props.trigger}</button>
<React.Fragment>
{this.state.showPopup?<Popup onExit={this.togglePopup} >{this.props.component}</Popup>:null}
</React.Fragment>
</React.Fragment>
)
}
}
export default PopupBuilder;
Actually, this looks like more general DOM/JavaScript question.
Consider such case:
const span = document.createElement('span');
span.innerText = 'hello';
span.getBoundingClientRect() // -> { width: 0, height: 0, top: 0, … }
This is an indicator that you don't know the dimensions of the element until it is in DOM (Rendered in react);
document.body.appendChild(span);
span.getBoundingClientRect(); // -> {width: 50, height: 16, …}
My recommendation to you in this case are:
Child component should accept a property (function) from Parent one
Use React "ref" feature to find actual dimensions of Child element
Call the function in 'componentDidMount' (use componentDidUpdate if child can change dynamically), passing it child component dimensions.
If you don't have access to child component. You may wrap it like this:
// Popup.tsx
class Popup .... {
...
render() {
<Measurer>{this.props.children}</Measurer>
}
}
and implement the logic of fetching dimensions in it. Measurer is a direct child of Popup and their communication can be controlled by you.
I'm working on a filter for a ListView, a way to be able to sort/order/etc the items. Basically I'm saving the parameters in state and they're updated via some toggles/select-fields on a <Modal>.
The modal has a cancel & apply button. If you select apply after changing filters, the ListView's contents would be updated. However if they were to select cancel after changing settings, they would be reverted to whatever it was before the filter modal was launched.
So I'm doing this:
// Update filterValues state
adjustFilterValue(filterSection, newValue) {
if ( this.state.hasAdjustedFilters === false ) {
const filterValues = this.state.filterValues;
this.setState({
hasAdjustedFilters: true
})
}
var newFilterValues = defaultFilterValues;
newFilterValues[filterSection] = newValue;
this.setState({
filterValues: newFilterValues
})
}
However whenever I adjust this.state.filterValues - newFilterValues get's updated too.
How can I save & isolate an object from state?
You can store your initial state by using lifecycle hooks:
componentDidMount() {
this._initialState = this.state;
}
Later on, in a case of revert, simply setState(this._initialState);
I'm attempting to get the width of a ref DOM element and set state to then use within the Component render. The problem comes because this width changes on user input and when I try setState within componentDidUpdate it starts an infinite loop and my browsers bombs.
I created a fiddle here http://jsbin.com/dizomohaso/1/edit?js,output (open the console for some information)
My thinking was;
Component Mounts, setState: refs.element.clientWidth
User inputs data, triggers render
shouldComponentUpdate returns true only if new.state is not equal to old.state. My problem is, I'm not sure where makes sense to update this state?
Any help will be much appreciated, thanks for reading!
Brad.
var component = React.createClass({
componentDidMount: function() {
//Get initial width. Obviously, this will trigger a render,
//but nothing will change, look wise.
//But, if this is against personal taste then store this property
//in a different way
//But it'll complicate your determineWidth logic a bit.
this.setState({
elWidth: ReactDOM.findDOMNode(this.refs.the_input).getBoundingClientRect().width
})
},
determineWidth: function() {
var elWidth = ReactDOM.findDOMNode(this.refs.the_input).getBoundingClientRect().width
if (this.state.elWidth && this.state.elWidth !== elWidth) {
this.setState({
elWidth: elWidth
})
}
},
render: function() {
var styleProp = {}
if (this.state.elWidth) {
styleProp.style = { width: this.state.elWidth };
}
return (
<input ref="the_input" onChange={this.determineWidth} {...styleProp} />
)
}
})
I like to use .getBoundingClientRect().width because depending on your browser, the element might have a fractional width, and that width will return without any rounding.