How to reload spfx web part after post()? - reactjs

I am creating spfx webpart with React framework.
I am having render method where all the controls were rendered. i am having a button, few checkboxes in the DOM which will send data to SharePoint using post method this.context.spHttpClient.post when user clicks on button. I am able to submit the data to SharePoint. But once I submit the data I am not able to reload the spfx webpart. I have to reload the web part again without reloading the page. I have tried to call the render method again. Of course it may not be correct way, so it is not working.

You can either call a force reload or set state as shown here and here
"By default, when your component’s state or props change, your component will re-render. If your render() method depends on some other data, you can tell React that the component needs re-rendering by calling forceUpdate().
Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render()."

You should use this methods:
componentDidMount()
componentDidUpdate(prevProps, prevState, snapshot)
componentWillUnmount()
shouldComponentUpdate(nextProps, nextState)
More info here https://reactjs.org/docs/react-component.html.
For example, in a project i have a list of buttons and i do a click so i send the event to another component that use the state:
LeftPanel.tsx :
import * as React from 'react';
import { Button } from 'react-bootstrap';
import { Event } from '../interfaces/Event';
import '../components/GruposDeColaboracion.module.scss';
import '../css/leftPanel.css';
export class LeftPanel extends React.Component {
constructor(public props, public context) {
super(props, context);
}
private getAllGroups = (e) => {
this.clearState();
this.setActive('allGroups');
this.props.setEvent(Event.GET_ALL_GROUPS);
//e.stopPropagation();
}
public render():JSX.Element{
return (
<div className="list-group">
<Button
id="allGroups"
onClick={this.getAllGroups}
className="list-group-item rounded-0 list-group-item-action btn btn-default active">
Todos
</Button>
</div>
);
}
}
MainPanel.tsx:
import * as React from 'react';
import { LeftPanel } from '../views/LeftPanel';
import { CenterPanel } from '../views/CenterPanel';
import { IHomeState } from '../interfaces/IHomeState';
export class MainPanel extends React.Component<{}, IMainState> {
constructor(public props, public context) {
super(props, context);
this.state = {
event: null
};
}
private getEvent = (event) => {
this.setState({ event: event });
}
public shouldComponentUpdate(nextProps, nextState): boolean {
if (this.state.event != nextState.event) {
return true;
}
return false;
}
public render(): JSX.Element {
return (
<div className="row">
<div className="col-md-2" style={{ maxWidth: '250px' }}>
<LeftPanel setEvent={this.getEvent} />
</div>
<div className="col-md-10">
<CenterPanel event={this.state.event} context={this.props.context} />
</div>
</div>
);
}
}
CenterPanel.tsx :
export class CenterPanel extends React.Component<{}, ICenterPanelState> {
constructor(public props, public context) {
super(props, context);
}
public componentWillMount() {
this.state = {
render: <Spinner />
};
}
public componentWillReceiveProps(nextProps) {
if (nextProps.event == Event.GET_ALL_GROUPS) {
let dataForRender = 'Hello';
this.setState({
render: <ResponseHandler data = {dataForRender}/>
});
}
}
public render():JSX.Element{
return(
<div>
{this.state.render}
</div>
);
}
}
ResponseHandler.tsx :
import * as React from 'react';
export class ResponseHandler extends React.Component {
constructor(public props, public context) {
super(props, context);
}
public render():JSX.Element {
return (
<div>
{this.props.data}
</div>
);
}
}
In this example you can see:
Left panel use this.props.setEvent('custom event') to send the event from MainPanel.
In main panel private getEvent = (event) => {...} recive the event and change the state and in render method i have: . You can see the event={this.state.event} that change the prop event in the class CenterPanel.
In center panel public componentWillReceiveProps(nextProps) {....} recive the event and and use state to render.

Related

How to refresh only a single item in React?

I've this App React component (TypeScript):
class App extends React.Component {
public state = {
text: ''
}
constructor(props: any) {
super(props);
this.state = { text: 'something' };
}
public handleClick = () => {
const date = `new ! : ${new Date().toString()}`;
// tslint:disable-next-line:no-console
console.log('click ! : ' + date);
this.setState({
text: date
});
};
public render() {
return (
<div className="App">
<button onClick={this.handleClick}>Button</button>
<p>{this.state.text}</p>
<Hello />
</div>
);
}
}
And this Hello component:
export class Hello extends React.Component {
#lazyInject("nameProvider")
private readonly nameProvider: IProvider<string>;
public render() {
return <h1>Hello {this.nameProvider.provide()}!</h1>;
}
}
Where the nameProvider is a simple provider which return based on a random number a string.
When clicking the button, the Date gets updated, however also the Hello component is re-rendered each time.
Is this expected? If yes, please explain.
And can this behaviour be changed?
Check full example project here...
You can make Hello component a PureComponent by extending React.PureComponent, so that it doesn't re-render on every parent render
export class Hello extends React.PureComponent {
#lazyInject("nameProvider")
private readonly nameProvider: IProvider<string>;
public render() {
return <h1>Hello {this.nameProvider.provide()}!</h1>;
}
}

Sharing store change event between same hierarchical level child components

I am developing a simple React JS application for learning purpose. I just started learning React JS a few days ago. Now, I am having a problem with Flux Store. I need to share the change event across two child components on the same hierarchical level.
I have the parent component, called TodoComponent with the following definition
//Create
class TodoComponent extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div>
<div>
<ListComponent />
</div>
<AddItemComponent />
</div>
)
}
}
It has two child components called, ListComponent and the AddItemComponent. Moreover, I have a store with this definition.
import { EventEmitter } from 'events';
class DataStore extends EventEmitter{
constructor()
{
super();
this.todos = [
"Eat",
"Sleep",
"Die",
"Shower"
];
}
getAll(){
return this.todos;
}
addItem(newItem)
{
this.todos.push(newItem);
this.emit("change")
}
}
const dataStore = new DataStore;
export default dataStore;
It has a function for adding new item into the array and a function for fetching the array.
This is the ListComponent that is displaying the array of items from the DataStore flux store.
import React from 'react';
import TodoItem from './TodoItem';
import DataStore from './data.store';
class ListComponent extends React.Component{
constructor(props)
{
super(props)
this.state = { todos : DataStore.getAll() };
}
componentWillMount(){
DataStore.on('change', () => {
//do somethif
this.state = { todos : DataStore.getAll() };
})
}
render()
{
var deleteItem = (item) => {
this.deleteItem(item);
}
var editItem = (item) => {
this.editItem(item);
}
var addItem = (newItem) => {
this.addItem(newItem);
}
var todos = this.state.todos.map((item, index) => {
return (
<TodoItem item={item} addItem={addItem.bind(this)} deleteItem={deleteItem} editItem={editItem} />
)
});
return (
<ul>
{todos}
</ul>
)
}
deleteItem(item)
{
this.setState({ todos: this.state.todos.filter((listItem, index) => {
return listItem !== item;
}) });
}
editItem(item)
{
alert(item)
}
addItem(newItem)
{
DataStore.addItem(newItem);
}
}
module.exports = ListComponent;
It is updating the items in the change event of the DataStore store. But I am not calling the addItem function in the ListComponent. I am calling it in the AddItemComponent.
This is the definition of the AddItemComponent.
import React from 'react';
import DataStore from './data.store';
class AddItemComponent extends React.Component{
constructor(props)
{
super(props)
}
render()
{
return (
<form id="form-todo" onSubmit={this.addItem.bind(this)} action="post">
<input type='text' ref="newItem" />
<button>ADD</button>
</form>
);
}
addItem(e)
{
e.preventDefault();
DataStore.addItem(this.refs.newItem.value);
}
}
module.exports = AddItemComponent;
But when I trigger the addItem function in the AddItemComponent, the change event of the DataStore in the ListComponent is not triggered. Therefore, how can I synchronize the change event of Flux Store between two components exist on the same hierarchical level?
The solution I can think of is having the DataStore in the TodoComponent (parent component) and send the data and functions as props to the child component. I think, the code will become a bit messy in that way. Is that the only solution to do that?
Welcome to React! I recreated your example and your "change" event is firing in ListComponent, but to update the state in a component you should use this.setState(changes) rather than this.state = {changes}. Only use this.state = {} in the constructor to set the initial state. The setState method properly flows through the React lifecycle and causes the component to re-render using the new state. There is an official guide on React's state and lifecycle hooks here.

Better practice for identifying what part of state to change when having one event handler for multiple dynamically created fields?

In React, callback functions are often used to send a signal from a child to a parent that something has happened.
I am building an application where the user is able to dynamically create more fields, and all change events for all fields are served by the same callback function. See my code below. As you can see I am using event, name, and key to know what part of my state to change.
Editable component passes name to parent
import React, { Component } from "react";
export default class Editable extends Component {
constructor(props) {
super(props);
this.onBlur = this.onBlur.bind(this)
}
onBlur(event) {
this.props.handleBlur(event, this.props.name)
}
render() {
return (
<div>
<div
contentEditable={true}
onBlur={this.onBlur}
dangerouslySetInnerHTML={{ __html: this.props.html }}
/>
</div>
);
}
}
Subject component passes name and id to parent
import React, { Component } from "react";
import Editable from "./Editable";
export default class Subject extends Component {
constructor(props) {
super(props);
this.handleBlur = this.handleBlur.bind(this);
}
handleBlur(event, name) {
this.props.handleBlur(event, name, this.props.skillId);
}
render() {
return (
<div>
<p>subject</p>
<Editable
handleBlur={this.handleBlur}
html={this.props.subject}
name="subject"
/>
</div>
);
}
}
The callback in the parent has the callback it needs to update the correct skill[property] in the state.
import Skill from "./Skill";
export default class Skills extends Component {
constructor(props) {
this.handleBlur = this.handleBlur.bind(this);
}
handleBlur(event, name, key) {
// Shange the state
}
render() {
var skills = [];
this.props.skills.map(element => {
skills.push(
<Skill
key={element.id}
skillId={element.id}
subject={element.subject}
ability={element.ability}
handleBlur={this.props.handleBlur}
/>
);
});
return <div>{skills}</div>;
}
}
What is a better practice for identifying what part of state to change when having one event handler for multiple dynamically created fields?

render method not triggered with observable array update

Data loading with fetch method in Repo class fine, but I couldnt pass it component, actually I expect it to re-render of observer component but its not happenning. Here they are;
MenuComponent.tsx:
#observer
#inject('params')
class MenuComponent extends React.Component<{params?:IMenuModel[]}, {}> {
render() {
//params undefined.
var menuJSX : JSX.Element[] = this.props.params ? this.props.params.map((item:IMenuModel, i:number)=>{
return (<li key={item.Id}>{item.itemName}</li>)
}):[];
return render(){...}
MenuRepo.tsx:
class MenuRepo {
#observable menuItems?: IMenuModel[];
constructor() {
this.getItems();
}
#action getItems(): void {
fetch(`..`).then((response: Response): Promise<{ value: IMenuModel[] }> => {
this.menuItems = [
{ Id: 1, itemName: 'test-item1', childItems: [] }
];//property setted here..
})
}
}
export default new MenuRepo;
App.tsx;
import Menu from './components/MenuComponent';
import menuCodes from './components/MenuRepo';
class App extends React.Component<null, null> {
render() {
return (
<div className="App">
<Menu params = {menuCodes.menuItems}/>
</div>
);
}
}
export default App;
I checked execution order, jsx render method not re-render after setting observerable field(menuItems) in fetch.
try reversing the order here...from this
#observer
#inject('params')
to this...
#inject('params')
#observer
from documentation: When using both #inject and #observer, make sure to apply them in the correct order: observer should be the inner decorator, inject the outer. There might be additional decorators in between.

forceUpdate is not re-rendering children

I'm using the react, redux react-router stack for my webapp. In the top level component's(the component that renders on the root path) componentDidMount I'm subscribing to the store as shown below
import NotificationsList from './components/notifier';
import React from 'react';
let Spinner = ({
isVisible,
showSpinner,
solidBackdrop
}) => (
<div style={{opacity: solidBackdrop ? 1 : 0.5}} className={"spinner " + (isVisible ? '' : 'hide')}></div>
);
export default class AppPage extends React.Component {
static contextTypes = {
store: React.PropTypes.object,
router: React.PropTypes.object
};
handleDismissNotification(notification) {
this.context.store.dispatch({
type: 'REMOVE_NOTIFICATION',
data: notification
});
}
componentDidMount() {
this.context.store.subscribe(() => this.forceUpdate());
}
render() {
let state = this.context.store.getState();
let props = {
notifications: state.notifications,
handleDismiss: this.handleDismissNotification.bind(this)
};
return (
<div className="'apppage-container">
{this.props.children}
<NotificationsList {...props} />
<Spinner isVisible={state.initialFetchInProgress || state.requestInProgress}
showSpinner={!state.initialFetchInProgress} solidBackdrop={state.initialFetchInProgress}/>
</div>
);
}
}
this.props.children here renders the component shown below
import Header from './components/header';
import React from 'react';
class ContentPage extends React.Component {
static contextTypes = {
store: React.PropTypes.object
};
render() {
let user = this.context.store.getState().user;
return <div className="content-container">
<Header user/>
</div>
}
}
export default ContentPage;
The problem is that when the first time a render happens, everything goes fine. Then when the render happens through forceUpdate, the child component is not getting re-rendered.
I think I got it. Every container component should be subscribed to the store separately. So accordingly, ContentPage should also have
componentDidMount() {
this.context.store.subscribe(() => this.forceUpdate());
}
As you replied to yourself, indeed the container component should subscribe to the store , but in addition to the subscription, it's good practice for the the container to also unsubscribe when unmounted :
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(() => this.forceUpdate());
}
componentWillUnmount() {
this.unsubscribe();
}

Resources