React State not available from Parent? - reactjs

I have a form with a child component that renders as a table.
ParentComponent extends React {
state = {
anArray: []
}
<ParentComponent>
<table>
map ( thing => <ChildComponent {someFunction= this.updateFunction;} />
When ChildComponent maps the data to individual TD's. In my onChange in the ChildComponent, I'm invoking
onChange = this.props.someFunction();
and the code is hitting my breakpoint which is great. It calls someFunction in the ParentComponent. In someFunction, I'm trying to access the parent's state so I can match the onChanged TD with the proper index in the array but I'm getting undefined.
someFunction(id) {
const index = this.state.anArray.findIndex( x => x.id === id) ;
if (index === -1)
// handle error
console.log("DIDN'T FIND ID: " + id);
});
}
Why wouldn't I have access to state on the function invocation from the ChildComponent? I expected to be able to access it.

It's not clear from the posted code, but I guess you haven't bind the someFunction and you have the context of the child, instead of parent's.
ParentComponent extends React {
constructor(props){
super(props)
this.someFunction = this.someFunction.bind(this)
}
someFunction(){
...
}
render(){
...
}
}
If you have the necessary babel plugins you can even do
ParentComponent extends React {
someFunction = () => {
...
}
render(){
...
}
}

Related

React: Mutating instance of Ref object in child components

class Parent extends React.Component {
constructor(props) {
super(props)
this.data = React.createRef()
}
componentDidMount() {
this.data.current = []
}
render() {
return <Child data={data} />
}
}
const Child = ({ data }) => {
const onDelete = (idx) => {
data.splice(idx, 1)
}
return (
<button onClick={(index) => onDelete(index)}>Delete</button>
)
}
I need this ref to be an array for some complicated to explain reason, although the code is doing the job I am not sure if it is right to mutate the ref prop in the child component. My question is: If the prop I am passing to the child component is not a state in the parent, then is it okay to mutate it? Or in refs case does that work differently? Thanks in advance for any help!

LocalStorage in state of the component

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.

Render Props - need to execute function after some component configuration

UPDATE, SOLUTION FOR NOW:
I moved scopes to the state and now the scopes data is up to date.
I am using render prop with the new context API. To make it easier, lets say that API got two methods. Method A is used by ChildComponent via Context API, and methodB is used as render prop.
The problem is that I need on init below order:
ChildComponent runs methodA from context API
Component property: this.scopes is populated
When methodB runs (from render props), it knows about this.scope
For now, methodB runs before the this.scope is populated (this.scope = {}) by methodA
I tried with setTimeout, but I don't think it is the best idea...
class Component extends React.Component{
scopes = {};
render(){
const api = {
methodA: (name, fields) => {
this.scopes[name] = fields;
},
methodB: (name) => {
console.log(this.scopes[name])
}
}
return (
<ComponentContext.Provider value={{ api }}>
{typeof children === 'function' ? children(api) : children}
</ComponentContext.Provider>
);
}
}
/************* CHILD COMPONENT */
class ChildComponent extends React.Component{
static contextType = ComponentContext;
componentWillMount() {
const { api } = this.context;
api.methodA(name, this.fields);
}
render() {
const { children } = this.props;
return children;
}
}
/************* COMPONENTS IMPELENTATION WITH THE PROBLEM */
<Component>
{(api) => (
<ChildComponent name="foo"> // it should at first add foo to the Component this.scope;
<div>Some Child 1</div>
</ChildComponent>
<ChildComponent name="bar"> // it should at first add bar to the Component this.scope;
<div>Some Child 2</div>
</ChildComponent>
{api.methodB(foo)} // not working because for now this.scopes is empty object (should be: {foo: someFields, bar: someFields})
)}
</Component>
I expect to result this.scope = {foo: ...someFields, bar: ...someFields }, for now this.scope= {} after initial run, next invocation of methodB works okey, and (this.scope = {foo: ...someFields, bar: ...someFields}.
Thank you for any tips.
You add and use the scope in the same lifecycle, thus using the old version of passed context. You can move your api.methodB(foo) from Render() method to componentDidUpdate() step, which will ensure you have a new context when it executes.
In case the initialization occurs only once and synchronously, parent Component can be considered initialized when it is mounted.
If the purpose of methodB is to return data that a parent was initialized with, parent's state should be updated on initialization. It's an antipattern to store component's state outside this.state. There may be a flag that definitely indicates that the initialization was completed:
class Component extends React.Component{
state = {
scopes: {},
init: false,
methodA: (name, fields) => {
if (this.state.init) return;
this.state.scopes[name] = fields;
},
methodB: (name) => this.state.init ? this.scopes[name] : null
};
componentDidMount() {
this.setState({ init: true });
}
render(){
return (
<ComponentContext.Provider value={this.state}>
...
</ComponentContext.Provider>
);
}
}
This way api.methodB(foo) will return null, unless the initialization is completed.

Passing value to props reactjs

I am trying pass value to my child components. The value that I am getting is coming from the an API that I called in my parent component and being called in the componentDidMount but the problem is the child components is not reading the props I am passing in his own componentDidMount, its only getting blank even in the reactdevtool it passing correct values. I solved this before but cannot remember what I did can you help. Thanks
Child:
componentDidMount() {
const {
events
} = this.props;
console.log(events)
}
Parent:
class App extends Component {
componentDidMount() {
let self = this;
GetAllMainItems().then(function(GetAllMainItemsResults) {
let MainObject = self.state.MainObject;
self.setState({
GetAllMainItemsResults
});
}
}
render() {
constructor() {
super();
this.state = {
MainObject: []
};
}
return ( <
div className = "App row" >
<
Calendar events = {
this.state.MainObject
}
/>
<
/div>
);
}
There are a few things you need to review.
constructor should be outside of render method.
You do not have to use let self = this. you can just do this.setState({...}) there.
Look at your GetAllMainItems callback. I don't know what you get
there. but you are definitely not setting mainObject in your state.
Instead, you will have this.state.GetAllMainItemsResults.
Recommendations
Try to understand object destructuring.
Use arrow functions
Hope it helps.
Parent Component
```
class App extends Component {
state = {
mainObject: ""
};
componentDidMount() {
GetAllMainItems().then(response => {
this.setState({
mainObject: response
});
});
}
render() {
const { mainObject } = this.state;
return (
<div className="App row">
<Calendar events={mainObject} />
</div>
);
}
}
The problem you are having is that your child component is re-rendering when it receives new events props.
Try adding a componentDidUpdate method to see these props updating:
componentDidUpdate(prevProps, prevState) {
console.log(prevProps, prevState);
console.log('events:', prevProps.events, this.props.events);
}

Children ref undefined react native

I created a component wrapper around ViewPagerAndroid (simplified version)
class TabView extends Component {
constructor(props){
super(props)
this.state = { position: 0 }
}
changePage = (key) => {
this._pagerRef.setPage(key)
this.setState({position: key})
}
render(){
return(
<ViewPagerAndroid ref={(ref) => this._pagerRef = ref}>
{ this.props.scenes }
</ViewPagerAndroid>
)
}
}
I want to trigger changePage from outside the component (eg from: <TabView ref={(ref) => this._ref = ref} />, and run this._ref.changePage(key)).
However, each time I try to do so, this._pagerRef is undefined inside the changePage function of TabView.
What am I missing ?
There is a more idiomatic React solution to the problem you are trying to solve -- namely making TabView a controlled component and setting ViewPager page on componentDidUpdate:
class TabView extends Component {
componentDidUpdate = ({ page }) => {
// call setPage if page has changed
if (page !== this.props.page && this._pagerRef) {
this._pagerRef.setPage(page);
}
};
render() {
return (
<ViewPagerAndroid
initialPage={this.props.page}
ref={ref => this._pagerRef = ref}
onPageSelected={e => this.props.pageChanged(e.nativeEvent.position)}
>
{this.props.scenes}
</ViewPagerAndroid>
);
}
}
You can then move the current page tracking to the parent component's state and pass it down to TabView as a prop, along with a handler that updates it when the value changes:
render() {
return (
<TabView
page={this.state.page}
pageChanged={page => this.setState({page})}
/>
)
}
You're trying to access the ref from outside of the component which has no instance to it.
Therefore you need to pass it as a prop from the parent component itself. Also you need to move the changePage to the parent component to access it from outside.
Parent
changePage = (key) => { //... Call the function here
this._pagerRef.setPage(key)
this.setState({position: key})
}
accessRef (ref) {
this._pagerRef = ref . //... Bind the ref here
}
<TabView passRef={this.accessRef} /> //...Pass the ref here
Child
<ViewPagerAndroid ref={this.props.passRef}> . // ... Set the ref here
{ this.props.scenes }
</ViewPagerAndroid>

Resources