I have a component that receives images as props, performs some calculation on them, and as a result I need to update its class. But if I use setState after the calculation, I get the warning that I shouldn't update state yet... How should I restructure this?
class MyImageGallery extends React.Component {
//[Other React Code]
getImages() {
//Some calculation based on this.props.images, which is coming from the parent component
//NEED TO UPDATE STATE HERE?
}
//componentWillUpdate()? componentDidUpdate()? componentWillMount()? componentDidMount()? {
//I CAN ADD CLASS HERE USING REF, BUT THEN THE COMPONENT'S
// FIRST RENDERED WITHOUT THE CLASS AND IT'S ONLY ADDED LATER
}
render() {
return (
<div ref="galleryWrapper" className={GET FROM STATE????}
<ImageGallery
items={this.getImages()}
/>
</div>
);
} }
You should put your logic into componentWillReceiveProps (https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops) so as to do a prop transition before render occurs.
In the end what we did was run the logic in the constructor and then put the class into the initial state:
class MyImageGallery extends React.Component {
constructor(props) {
super(props);
this.getImages = this.getImages.bind(this);
this.images = this.getImages();
this.state = {smallImgsWidthClass: this.smallImgsWidthClass};
}
getImages() {
//Some calculation based on this.props.images, which is coming from the parent component
this.smallImgsWidthClass = '[calculation result]';
return this.props.images;
}
render() {
return (
<div className={this.state.smallImgsWidthClass }
<ImageGallery
items={this.images}
/>
</div>
);
}
}
Related
I've got 2 components and want to get Panel_Menu element in another child component to do some stuff with it.
class Panel extends Component {
constructor(props){
super(props);
this.menuRef = React.createRef();
}
componentDidMount() {
console.log (this.menuRef.current)
// works correctly
}
render() {
return(
<>
<Panel_Menu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current}/>
</Panel_Menu>
</>
)
}
}
class Menu extends Component {
constructor(props) {
super(props);
}
isSame = () => {
const isSlideClass = this.props.item;
console.log(isSlideClass)
// is null
// expected output: → <div class="panel-menu"></div>
}
render() {
return (
<Left_Menu >
<Panel_Menu_Items className="test" onClick={this.isSame} />
</Left_Menu>
);
}
}
How can I update data in done render() to reach my goal?
Or... how can I get element instantly in external Component (Menu in this case) to do some stuff with it?
Issue
The issue here is that React refs, when attached on the initial render, will be undefined during the initial render. This means that item={this.menuRef.current} will enclose the initial undefined ref value in the click handler of the child.
Solution
It's simple, you really just need to trigger a rerender to reenclose an updated React ref value. You can either add some state to the Panel component and update it in the componentDidMount lifecycle method, or just issue a forced update.
class Panel extends Component {
menuRef = createRef();
componentDidMount() {
console.log(this.menuRef.current);
this.forceUpdate(); // <-- trigger rerender manually
}
render() {
return (
<>
<PanelMenu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current} />
</PanelMenu>
</>
);
}
}
Demo
I have two components Parent and Children. I want to see on my screen actual value of localStorage.getItem("myEl"). Parent state is storage:localStorage.getItem("myEl"). I change the "myEl" in localeStorage in Children component. Unfotunately Parent component not re-renders after "myEl" is changed but it works after I perform some action, such as changing the state again. I know that the problem is that setState is asinc but i don't know how to fix the problem.
For example,
Parent:
class Parent extends React.Component {
constructor() {
super();
this.state = {storage:localStorage.getItem("myEl")};
}
render(){
return <div>
<Child/>
<p>{this.state.storage}</p>
</div>
}
}
Child:
let i=0;
class Child extends React.Component {
render() {
return (
<button onClick={() => {
localStorage.setItem("myEl",i);
i++;
}}>click me</button>
);
}
}
react is not listening to changes in localStorage that is why parent component don't know when child component changes the value in localStorage.
To fix this you have to path your child component onClick function from parent this way:
class Parent extends React.Component {
constructor() {
super();
this.state = {storage:localStorage.getItem("myEl")};
}
handleChildClick = (count) => {
localStorage.setItem("myEl", count);
this.setState({ storage:localStorage.getItem("myEl") });
}
render(){
return <div>
<Child onClick={this.handleClick} />
<p>{this.state.storage}</p>
</div>
}
}
let i=0;
class Child extends React.Component {
render() {
return (
<button onClick={() => {
this.props.onClick(i);
i++;
}}>click me</button>
);
}
}
in case you need this value in other components consider using redux with react-redux containers to have a global storage available to you in any place of the react app.
Component should receive an state or prop in order to rerender itself, in your case it receive none of them. You should not update the localStorage and expect that your component is going to be reRendered with a new value from local storage, you could write a handler for your button in order to save the incremented value into your localstorage. Like below:
class App extends React.Component {
constructor() {
super()
this.state = { _val: 0 }
}
componentDidMount = () => {
const valFromLocalStorage = localStorage.getItem("myEl") || this.state._val
this.setState({ _val: valFromLocalStorage })
}
handleINC = e => {
const _valFromState = this.state._val
const _val = _valFromState++
localStorage.setItem("myEl", _val)
}
render() {
return(
<div>
<button onClick={this.handleINC}>increment value!</button>
</div>
)
}
}
By the way, in componentDidMount you get the value from localStorage or if it was falsy you get the default value from your state. Then in button handler function you get the value from state and increment it and set it in your localStorage in case of component use cases in future, when user closes the tab and opens our website after a while the localstorage data is not been cleared, then this component will get the value from there.
I'm using the react to build some input forms.
While all children inputs have and their own states to store values I have no idea how to process the to a parent.
Here's example:
class FormComponent extends Component {
constructor(props) {
super(props);
this.state = {
title: null,
someAmount: null
}
}
render() {
let me = this;
return (
<div>
<TextField
value={me.state.title}
onChange={(proxy, value) => {
me.setState({title: value})
me.hanleChnage();
}
}
/>
<TextField
value={Number.parseFloat(me.state.someAmount)}
onChange={(proxy, value) => {
if (!isNaN(Number.parseFloat(value))) {
me.setState({someAmount: value})
me.hanleChnage();
}
}
}
/>
</div>
)
}
handleChange() {
//Calling the parent
//State here is outdated
this.props.onUpdate && this.props.onUpdate(this.state);
}
}
export default FormComponent;
Or where I can find some example of usage of compex forms with much inputs in react.
Thanks!
Sounds like you need to consider moving some of your state into the parent components. The React docs have a good article about this.
To summarize, you can pass your hanleChnage(); function as a prop to your child components if you declare the function in your parent.
function handleChange() { //do something... }
...
<ChildComponent parentOnChange={this.handleChange.bind(this) />
As your components grow in complexity, you might consider using Redux for state management, thus serving as a single source for all state in your application.
Set a child property, (e.g. callParentProperty) to reference a function in the parent component (e.g. parentFunction).
class ParentComponent extends Component{
parentFunction(parameter) {
console.log("This is the form value");
console.log(parameter);
}
render() {
return <FormComponent callParentFunctionProperty={this.parentFunction.bind(this)} />
}
}
class FormComponent extends Component {
...
handleChange() {
...
let formValue = this.state.someAmount;
this.props.callParentFunctionProperty(formValue);
}
}
There is simple scenario I updated a value in parent which passed to child component and expected cWRP method firing but not. here code below;
Parent component:
class App extends Component {
changeProps(){//interpreter jumps here fine..
debugger
this.appState.index=15 //update props value
}
render() {
return (
<div className="App">
<EasyABC parentUpdateProps={this.changeProps} appState={this.props.appState} />
</div>
)
}
}
child component:
#observer
export default class EasyABC extends Component{
constructor(props){
super(props)
}
componentWillReceiveProps(nextProps){//why its not jump here after update props in parent?
debugger
}
playSound(){// when this method called, cWRP above should be invoked rigth?
this.props.parentUpdateProps()
}
render(){
return(
<div>
<a onClick={()=> this.playSound()}>Play Sound Again</a>
Edited: i am using mobx as state handler, but dont bother with it
You need to update the state of the component using setState and use the same for passing it to child component
class App extends Component {
constructor() {
this.state = {
index: 0,
};
this.changeProps = this.changeProps.bind(this);
}
changeProps(){
this.setState({
index: 15,
});
// this will update state (not props)
}
render() {
return (
<div className="App">
<EasyABC
parentUpdateProps={this.changeProps}
appState={...this.state}
/>
</div>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
You are updating the state wrongly. You have to use setState e.g.
changeProps() {
this.setState({
index: 15
});
}
You have to dereference the observable value in the render function or it will not fire the will receive props because the component is not actually using it to render.
You could just do something like this:
render() {
if (this.props.appState.index) {
return <div>Play Sound Again</div>;
}
return <div>Play Sound</div>;
}
It really doesn't matter how you use it, but that you access it within the call stack of the render method.
I'm working on React Redux app and I have quite fundamental question about some kind of best practises.
I have MainComponent (kind of container) where I'm fetching data on componentDidMount:
class MainComponent extends React.Component {
componentDidMount(){
this.fetchData()
}
fetchData() {
this.props.fetchDataAction()
}
render() {
return (
<div>
<ChildComponent1 />
<ChildComponent2 />
</div>
)
}
}
export default connect(mapStateToProps,{fetchDataAction})(MainComponent)
How to pass fetched data to ChildComponents? What is the best practise? Two possible solutions are (IMHO - maybe more?)
First solution:
class MainComponent extends React.Component {
...
render() {
return (
<div>
<ChildComponent1 dataOne={this.props.data.one} />
<ChildComponent2 dataTwo={this.props.data.two} />
</div>
)
}
...
Second solution - connect ChildComponents to store which is updated by fetchDataAction() in MainComponent:
class ChildComponent1 extends React.Component {
render() {
return (
<div>
{this.props.one}
</div>
)
}
}
function mapStateToProps(state){
return (
one: state.one
)
}
export default connect(mapStateToProps,null)(ChildComponent1)
Now I use first solution when ChildComponents do not fire actions which update store and second solution when they do. But I'm not sure if it is proper approach.
If you have multiple child components and you have to pass a part of fetched data to different child components ; I would suggest keep the parent component as single point of source.
You can try something like:-
class MainComponent extends React.Component {
constructor(){
super()
this.state = {
data : {}
}
}
componentDidMount(){
this.fetchData()
}
fetchData() {
this.props.fetchDataAction()
}
componentWillReceiveProps(nextProps){
//once your data is fetched your nextProps would be updated
if(nextProps.data != this.props.data && nextProps.data.length>0){
//sets your state with you data--> render is called again
this.setState({data:nextProps.data})
}
render() {
//return null if not data
if(this.state.data.length === 0){
return null
}
return (
// it should have keys as one and two in api response
<div>
<ChildComponent1 data={data.one}/>
<ChildComponent2 data={data.two}/>
</div>
)
}
}
function mapStateToProps(state){
return (
data: state
)
}
export default connect(mapStateToProps,{fetchDataAction})(MainComponent)
I feel all logic stays at one place this way. Say if you plan to add to add few more child components in future,you only need to add a line of code above and few changes in API. However if you read in each component you have connect that component to store again which makes it more complex.
So if you dont have any other logic in your child components apart from getting data I would keeping this logic in the parent component.