How to update state into Provider context in class in React - reactjs

i have a problemn, i need to pass an array to class DragAndDropProvider but not working, i can see value in console log but not is possible update state with value, received error as:
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
moment in that i call the class
Index
const documents = UseGetDocuments();
const { state, setItemsArray } = useContext(DragAndDropContext);
setItemsArray(documents);
DragAndDropProvider
export class DragAndDropProvider extends Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
items: [],
moveItem: this.moveItem,
setItems: this.setItems
};
}
an_independent_function(documents: any) {
console.log(documents);
this.setState({ items: documents })
}
render() {
return (
<DragAndDropContext.Provider
value={
{
state: this.state,
setItemsArray: (documents: any) => {
this.an_independent_function(documents);
},
}
}>
{this.props.children}
</DragAndDropContext.Provider>
);
}

When using Class components you need to bind 'this' for it to work in your callback function.
constructor(props: any) {
...
this.an_independent_function = this.an_independent_function.bind(this);
}

Related

ReactJS Change Sibling State via Parent

My React structure is
- App
|--SelectStudy
|--ParticipantsTable
In SelectStudy there is a button whose click triggers a message to its sibling, ParticipantsTable, via the App parent. The first Child->Parent transfer works. But how do I implement the second Parent->Child transfer? See questions in comments.
App
class App extends Component {
myCallback(dataFromChild) {
// This callback receives changes from SelectStudy Child Component's button click
// THIS WORKS
alert('SelectStudy Component sent value to Parent (App): ' + dataFromChild.label + " -> " + dataFromChild.value);
// QUESTION: How to Update State of ParticipantsTable (SelectStudy's Sibling) next?
// ........................................................
}
render() {
return (
<div className="App">
<SelectStudy callbackFromParent={this.myCallback}></SelectStudy>
<ParticipantsTable></ParticipantsTable>
</div>
);
}
SelectStudy
class SelectStudy extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
selectedStudy: null,
isButtonLoading: false
};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
render() {
const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.state;
return <Button onClick={this.handleButtonClick}>Search</Button>;
}
handleButtonClick = () => {
this.props.callbackFromParent(this.state.selectedStudy);
}
}
ParticipantsTable - this needs to receive a certain variable, e.g. study in its State
class ParticipantsTable extends React.Component {
constructor(props) {
//alert('Constructor');
super(props);
// Initial Definition of this component's state
this.state = {
study: null,
items: [],
error: null
};
}
// THIS METHOD IS AVAILABLE, BUT HOW TO CALL IT FROM App's myCallback(dataFromChild)?
setStudy = (selectedStudy) => {
this.setState({study: selectedStudy});
}
render() {
return ( <div>{this.state.study}</div> );
}
}
The state should live definitively at the App level, not in the child. State needs to live one level above the lowest common denominator that needs access to it. So if both SelectStudy and ParticipantsTable need access to the same bit of state data, then it must live in their closest common ancestor (or above).
This is a core concept of React, known as "lifting state up", so much so that it has its own page in the official React documentation.
In your case, it would look something like this. Notice how state lives in only one place, at the <App /> level, and is passed to children via props.
import React from 'react';
class App extends React.Component {
// State lives here at the closest common ancestor of children that need it
state = {
error: null,
isLoaded: false,
items: [],
selectedStudy: null,
isButtonLoading: false
};
myCallback = (dataFromChild) => {
this.setState(dataFromChild);
};
render() {
return (
<div className="App">
{/* State is passed into child components here, as props */}
<SelectStudy data={this.state} callbackFromParent={this.myCallback}></SelectStudy>
<ParticipantsTable study={this.state.selectedStudy} />
</div>
);
}
}
class SelectStudy extends React.Component {
handleButtonClick = () => {
// Here we execute a callback, provided by <App />, to update state one level up
this.props.callbackFromParent({ ...this.props.selectedStudy, isButtonLoading: true });
};
render() {
const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.props.data;
return <Button onClick={this.handleButtonClick}>Search</Button>;
}
}
// This component doesn't need to track any internal state - it only renders what is given via props
class ParticipantsTable extends React.Component {
render() {
return <div>{this.props.study}</div>;
}
}
I think what you need to understand is the difference between state and props.
state is internal to a component while props are passed down from parents to children
Here is a in-depth answer
So you want to set a state in the parent that you can pass as props to children
1 set state in the parent
this.state = {
value: null
}
myCallback(dataFromChild) {
this.setState({value: dataFromChild.value})
}
2 pass it as a prop to the children
class ParticipantsTable extends React.Component {
constructor(props) {
super(props);
this.state = {
study: props.study,
items: [],
error: null
};
}
Also, although not related to your question, if you learning React I suggest moving away from class-based components in favour of hooks and functional components as they have become more widely used and popular recently.

React: updating controlled child component

I'm trying to create some React components using TypeScript, one of which handles user input (let's call it MyCtrl), and the other reacting to this user input and updating the backend (MyUpdatingCtrl), both being controlled components. My issue is that when updating the backend fails I need to revert the value in the user input component, but since it keeps the value in its state I can't update it from the outer component. So how do I handle this case correctly?
Note that I simplified my situation to explain my issue more easily. The following bit of code does not represent my actual project, but illustrates the issue in the simplest way I could think of.
MyCtrl:
export interface MyCtrlProps {
Value: string;
OnValueChanged: (newValue: string) => void;
}
interface MyCtrlState {
CurrentValue: string;
}
export class MyCtrl extends React.Component<MyCtrlProps, MyCtrlState>{
constructor(props: MyCtrlProps) {
super(props);
this.state = { CurrentValue: props.Value };
}
render() {
return <input onChange={this.onInputChanged} value={this.state.CurrentValue} />;
}
private onInputChanged = (e: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({ ...this.state, CurrentValue: e.target.value },
() => this.props.OnValueChanged(this.state.CurrentValue));
};
}
MyUpdatingCtrl:
export interface MyUpdatingCtrlProps {
Value: string;
}
interface MyUpdatingCtrlState {
CurrentValue: string;
PreviousValue: string;
}
export class MyUpdatingCtrl extends React.Component<MyUpdatingCtrlProps, MyUpdatingCtrlState>{
constructor(props: MyUpdatingCtrlProps) {
super(props);
this.state = {
CurrentValue: props.Value,
PreviousValue: props.Value
};
}
render() {
return <MyCtrl Value={this.state.CurrentValue} OnValueChanged={this.onValueChanged} />
}
private onValueChanged = (newValue: string): void => {
try {
// update backend
}
catch (error) {
// How do I reset the value of MyCtrl to the previous value here?
}
};
}
Setting the state.CurrentValue in the catch block inside MyUpdatingCtrl.onValueChanged of course won't update the value in MyCtrl, so what should I do?
first of all, you can't get the updated state like that, use
this.props.OnValueChanged(e.target.value);
because setState is an async function and can't get its updated value in the function you are calling it except by using the callback method

Passing state to sibling component without using Redux

So I have a component called "itemSelection" which contains a state with a property called "allItems" of type array
constructor(props){
super(props);
this.state = {
allItems: []
}
}
Then I have a component called "methods" which contains a function that returns a value
selectMethod = (e) => {
const x = e.target.getAttribute("data");
this.setState({method: x}, () => console.log(this.state.method));
}
So, What I want to do is to bring the value of propery "method" and push it to the "allItems" array aside with its current state.
The solution is to lift state up: the shared state (here the items) should be kept by the closest ancestor of both components. This ancestor then passes the state to the children via props, along with a callback function that children can use to mutate the state.
For instance in pseudo-code:
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
allItems: []
}
this.onSelect = this.onSelect.bind(this)
}
onSelect(item) {
this.setState({allItems: this.state.allItems.push(item)})
}
render() {
return (
<Child1 items={this.state.allItems}/>
<Child2 onSelect={this.onSelect}/>
)
}
}
class Child1 extends React.Component {
render() {
return (
{this.props.items.map(i => i.name)}
)
}
}
class Child2 extends React.Component {
render() {
return (
<button onClick={this.props.onSelect(...)}>button</button>
)
}
}
In the itemSelection component, create a function that will update allItems. Then, pass that function in a property to the method component and call it from there.

react-data-components renders component (a button) but can't access state or function defined outside the render method

I am using react-data-components to render a table. Inside the table I render a delete button. However, in the render method, it has no access to 'this' keyword and thus can't access any handler function on button click. I know how to communicate between parent and child components, but this doesn't seem to fall into that category. The code compiles but fails at run time. The error is: Uncaught TypeError: Cannot read property 'handleClick' of undefined
Any help is appreciated.
Here's my code:
interface MyComponentProps extends RouteComponentProps {
appState: AppState;
}
#inject('appState')
class MyComponent extends React.Component {
constructor(props: MyComponentProps, context: object) {
super(props, context);
}
renderLink(val: any, row: any) {
console.log(this); //'this' is undefined
return (
<button onClick={this.handleClick}>Delete</button>
);
}
handleClick() {
console.log('click');
// access appState here
}
render() {
var columns =
[
{ title: '', prop: 'Id', render: this.renderLink },
{ title: 'Name', prop: 'Name' }
];
let data = [];
// code to populate data
return (<DataTable keys="fileId" columns={columns} initialData={data} />);
}
}
export default MyComponent;
You have 2 options here.
1.- Call the handleClick as a arrow function, to bind the function.
onClick={() => this.handleClick()}
or
2.- Bind the function in the constructor:
constructor(props: MyComponentProps, context: object) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
}
The decision is yours, I use the 2nd when is a function called multiple times, and the first one when is a one-time function.

React child component can't get props.object

My parent component is like this:
export default class MobileCompo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
datasets: {}
};
this.get_data = this.get_data.bind(this);
}
componentWillMount() {
this.get_data();
}
async get_data() {
const ret = post_api_and_return_data();
const content={};
ret.result.gsm.forEach((val, index) => {
content[val.city].push()
});
this.setState({data: ret.result.gsm, datasets: content});
}
render() {
console.log(this.state)
// I can see the value of `datasets` object
return (
<div>
<TableElement dict={d} content={this.state.data} />
<BubbleGraph maindata={this.state.datasets} labels="something"/>
</div>
)
}
}
child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
console.log(this.props);
// here I can't get this.props.maindata,it's always null,but I can get labels.It's confusing me!
}
componentWillMount() {
sortDict(this.props.maindata).forEach((val, index) => {
let tmpModel = {
label: '',
data: null
};
this.state.finalData.datasets.push(tmpModel)
});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I tried many times,but still don't work,I thought the reason is about await/async,but TableElement works well,also BubbleGraph can get labels.
I also tried to give a constant to datasets but the child component still can't get it.And I used this:
this.setState({ datasets: a});
BubbleGraph works.So I can't set two states at async method?
It is weird,am I missing something?
Any help would be great appreciate!
Add componentWillReceiveProps inside child componenet, and check do you get data.
componentWillReceiveProps(newProps)
{
console.log(newProps.maindata)
}
If yes, the reason is constructor methos is called only one time. On next setState on parent component,componentWillReceiveProps () method of child component receives new props. This method is not called on initial render.
Few Changes in Child component:
*As per DOC, Never mutate state variable directly by this.state.a='' or this.state.a.push(), always use setState to update the state values.
*use componentwillrecieveprops it will get called on whenever any change happen to props values, so you can avoid the asyn also, whenever you do the changes in state of parent component all the child component will get the updates values.
Use this child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
}
componentWillReceiveProps(newData) {
let data = sortDict(newData.maindata).map((val, index) => {
return {
label: '',
data: null
};
});
let finalData = JSON.parse(JSON.stringify(this.state.finalData));
finalData.datasets = finalData.datasets.concat(data);
this.setState({finalData});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}

Resources