having trouble getting redux components to re-render - reactjs

I have 2 components (a form for inputing values and a react-table component to display the inputed values) that I am putting on a dashboard. When I enter the values into the form to update the redux store, I can see the changes from redux tools however, the table component doesn't update until I go to a different page and come back. Anyone know what I am doing wrong?
Here is my reducer. I don't believe I am mutating the state here.
Reducer:
const keysReducerDefaultState = [];
export default (state = keysReducerDefaultState, action) => {
switch (action.type) {
case 'ADD_KEYS':
return [
...state,
action.keyPair
];
case 'REMOVE_KEYS':
return state.filter(({ name }) => {
return name !== action.name;
});
default:
return state;
}
}
Component 1
class KeysImportForm extends React.Component {
constructor(props) {
super(props);
this.state = {
// type validation
name: "",
publicKey: "",
privateKey: "",
};
this.typeClick = this.typeClick.bind(this);
}
render() {
const { classes } = this.props;
return (
// a form that takes in 3 fields, name, publickey and privatekey
);
}
}
const mapDispatchToProps = (dispatch) => ({
addKeys: (keyPair) => dispatch(addKeys(keyPair))
});
export default withStyles(validationFormsStyle)(connect(undefined, mapDispatchToProps)(KeysImportForm));
Component 2
class KeysTable extends React.Component {
constructor(props) {
super(props);
const data = props.keys.map((prop, key) => {
return {
id: key,
name: prop.name,
publicKey: prop.publicKey,
privateKey: prop.privateKey,
};
});
this.state = {
data
};
}
render() {
const { classes } = this.props;
return (
<GridContainer>
<GridItem xs={12}>
<Card>
<CardHeader color="primary" icon>
<CardIcon color="primary">
<Assignment />
</CardIcon>
<h4 className={classes.cardIconTitle}>Key Pairs</h4>
</CardHeader>
<CardBody>
<ReactTable
data={this.state.data}
filterable
columns={[
{
Header: "Name",
accessor: "name",
minWidth: 10
},
{
Header: "Public Key",
accessor: "publicKey",
minWidth: 50
},
{
Header: "Private Key",
accessor: "privateKey",
minWidth: 50
},
{
Header: "Action",
accessor: "action",
minWidth: 10,
sortable: false,
filterable: false
}
]}
defaultPageSize={10}
showPaginationTop
showPaginationBottom={false}
className="-striped -highlight"
/>
</CardBody>
</Card>
</GridItem>
</GridContainer>
);
}
}
const mapDispathToProps = (dispatch, props) => ({
removeKeys: (id) => dispatch(removeKeys(id))
});
const mapStateToProps = (state) => {
return {
keys: state.keys
}
}
export default withStyles(styles)(connect(mapStateToProps, mapDispathToProps)(KeysTable));
Dashboard
class Dashboard extends React.Component {
state = {
value: 0
};
handleChange = (event, value) => {
this.setState({ value });
};
handleChangeIndex = index => {
this.setState({ value: index });
};
render() {
const { classes } = this.props;
return (
<div>
<KeysImportForm/>
<KeysTable/>
</div>
);
}
}
Dashboard.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(dashboardStyle)(Dashboard);

I'm not 100% sure, but it looks like you are having the following error:
In your constructor you do a (unnecessary) copy of you props to your state, which introduces the error and defeats the purpose of Redux:
const data = props.keys.map((prop, key) => {
return {
id: key,
name: prop.name,
publicKey: prop.publicKey,
privateKey: prop.privateKey,
};
});
This causes your data to only update when your constructor is called, which is when your component mounts (a.k.a. you reload your page).
Instead use your props directly as your data. Redux will cause your component to re-render every time the state changes.

Related

get element in react native

I want to create a Text element inside a View element, how do I link that? I've tried the following. (After typing inside the input, a search is made in the database and the result is translated into a text element).
class SearchScreen extends React.Component {
state = {
inputValue: "",
};
search() {
//Here I do the search in Firebase Realtime Database (it works)
var textElement = React.createElement(
Text,
{ style: { fontSize: 20 } },
[...] //Here inside is the retrieved data from the database
);
var resultView = useRef(resultView); //This doesn't work
ReactDOM.render(textElement, resultView);
}
setSearch = (inputValue) => {
this.setState({ inputValue }, () => this.search());
};
render() {
return (
<View>
<TextInput
onChangeText={(inputValue) => this.setSearch(inputValue)}
value={this.state.inputValue}
/>
<View ref="resultView">
</View>
</View>
)
}
}
export default SearchScreen;
ReactDom doesn't work with React Native.
Try something like this:
class SearchScreen extends React.Component {
state = {
inputValue: '',
resultText: '',
resultList: [],
};
search() {
//Here I do the search in Firebase Realtime Database (it works)
//[...] //Here inside is the retrieved data from the database
// Simulate request to the database
setTimeout(() => {
const databaseResultText = 'Hello World';
this.setState({
resultText: databaseResultText,
});
const databaseResultList = [
{
name: 'Bob',
},
{
name: 'Steve',
},
];
this.setState({
resultList: databaseResultList,
});
}, 1000);
}
setSearch = (inputValue) => {
this.setState({inputValue}, () => this.search());
};
render() {
return (
<View>
<TextInput
onChangeText={(inputValue) => this.setSearch(inputValue)}
value={this.state.inputValue}
/>
<View>
<Text>{this.state.resultText}</Text>
</View>
<View>
{this.state.resultList.map((item) => {
return <Text>{item.name}</Text>;
})}
</View>
</View>
);
}
}
export default SearchScreen;

Material-table ReactJs toggle detailPanel open/closing from actions

Is there a way to control the detailPanel opening/closing in material-table from the onClick() => {} action in the actions column ?
Eventualy I made it work by following this thread
export class Services extends React.Component {
constructor() {
super();
}
this.tableRef = React.createRef();
render() {
return (
<MaterialTable
tableRef={this.tableRef}
icons={tableIcons}
title= 'Service catalog table'
columns={[{ title: "Service Name", field: "nom_service", editable: 'never'}]}
actions={[
(rowData) => {
return ({
onClick: (event, rowData) => {
this.tableRef.current.onToggleDetailPanel( [rowData.tableData.id],
this.tableRef.current.props.detailPanel[0].render)
}
})}
]}
detailPanel={[
{
icon: ()=> null,
openIcon: ()=> null,
disabled:true,
render: rowData => {
return (<p>{rowData.nom_service}</p>)
}
}
]}
)
}
}

Delete an element by key from Array

I used library react-sortable-hoc for drag and drop element, but the library documentation does not have any actions for delete items. I want to delete, drag and drop item when click on close button. Which method is right for removing elements by key from object?
React
const SortableItem = SortableElement(({ value }: { value: string }, onRemove: any) =>
<div className="dragItems" style={{ background: 'gray' }}>
<img src={value} alt="" />
<button className="dragCloseBtn" onClick={() => onRemove(any)} />
</div>
);
const SortableList = SortableContainer(({ items }: { items: string[] }) => {
return (
<div className="dragAndDrop">
{items.map((value, index) => (
<SortableItem key={'item-${index}'} index={index} value={value} />
))}
</div>
);
});
constructor(props: any) {
super(props);
this.state = {
items: [
{
"id": 0,
"link": "https://via.placeholder.com/150"
},
{
"id": 1,
"link": "https://via.placeholder.com/150"
}
],
};
}
public onSortEnd = ({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex),
});
};
public onRemove(e: { target: { value: any; }; }) {
const array = [...this.state.items];
const index = array.indexOf(e.target.value)
if (index !== -1) {
array.splice(index, 1);
this.setState({items: array});
}
}
<SortableList items={this.state.items}
onSortEnd={this.onSortEnd}
lockAxis="xy"
axis="xy" />
UPDATED:
Hi there, I figured out what went wrong and made a successful remove event on your application.
Everything is illustrated with comments at this codesandbox.
=========
I modified this one, it should do the required using Array's filter method.
public onRemove(e: { target: { value: any; }; }) {
let array = [...this.state.items];
const intId = parseInt(e.target.value, 10);
array = array.filter(item => item.id !== intId);
this.setState({items: array});
}
So there were few problems in your code! You seemed to be confuse how react works with passing down props. You have to pass down the method required for remove. And you should bind it inside the class that you will be calling it.
onRemove should be bound to current context
onRemove should be passed down across the component tree
Check my //[NOTE]====> comments for additional explanation
Working code sandbox is here
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
arrayMove,
SortableContainer,
SortableElement
} from "react-sortable-hoc";
//[NOTE]====>value contains the relevent object containing the callback. Onclick call it with the relevant id
const SortableItem = SortableElement(
({ value }: { value: any }, onRemove: any) => (
<div className="dragItems" style={{ background: "gray", margin: "20px" }}>
<img src={value.link} alt="" />
<button className="dragCloseBtn" onClick={() => value.onRemove(value.id)}>
{" "}
X{" "}
</button>
</div>
)
);
const SortableList = SortableContainer(({ items }: { items: string[] }) => {
return (
<div className="dragAndDrop">
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} />
))}
</div>
);
});
class SortableComponent extends React.Component<{}, { items: string[] }> {
constructor(props: {}) {
super(props);
//[NOTE]====>Send the callback on each element bound to the current context
//This is like telling the function from where exactly the function will be called
this.state = {
items: [
{
id: 0,
link: "https://via.placeholder.com/150",
onRemove: this.onRemove.bind(this)
},
{
id: 1,
link: "https://via.placeholder.com/150",
onRemove: this.onRemove.bind(this)
}
]
};
}
public render() {
return <SortableList items={this.state.items} onSortEnd={this.onSortEnd} />;
}
public onSortEnd = ({
oldIndex,
newIndex
}: {
oldIndex: number;
newIndex: number;
}) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex)
});
};
//[NOTE]====>Use the id to filter out and set the new set of items
public onRemove(id) {
console.log(id);
let array = [...this.state.items];
const intId = parseInt(id, 10);
array = array.filter((item: any) => item.id !== intId);
this.setState({ items: array });
}
}
ReactDOM.render(<SortableComponent />, document.getElementById("root"));

How to add right click menu to react table row, and access its properties?

I've added react-table package to my project and everything is fine, but I also wanted to have a possibility to right click on a row and perform some actions on it (cancel, pause etc). I'm using React with Typescript but I hope it doesn't add any complexity.
My initial idea was to use react-contextify, however I can't find any working examples that would combine react-table and react-contextify together.
The only "working" example I have found is this one:
React Context Menu on react table using react-contexify
I ended up not using react-contextify and it "kind of works" but I'm not totally certain about this one as I sometimes keep getting exceptions like this:
Uncaught TypeError: Cannot read property 'original' of undefined
The code I have now is this:
const columns = [
{
Header: "Name",
accessor: "name"
},
{
Header: "Age",
accessor: "age",
Cell: (props: { value: React.ReactNode }) => (
<span className="number">{props.value}</span>
)
},
{
id: "friendName", // Required because our accessor is not a string
Header: "Friend Name",
accessor: (d: { friend: { name: any } }) => d.friend.name // Custom value accessors!
},
{
Header: (props: any) => <span>Friend Age</span>, // Custom header components!
accessor: "friend.age"
}
];
return (
<div>
<ContextMenuTrigger id="menu_id">
<ReactTable
data={data}
columns={columns}
showPagination={false}
getTdProps={(
state: any,
rowInfo: any,
column: any,
instance: any
) => {
return {
onClick: (e: any, handleOriginal: any) => {
const activeItem = rowInfo.original;
console.log(activeItem);
},
onContextMenu: () => {
console.log("contextMenu", rowInfo);
this.setState({
showContextMenu: true,
rowClickedData: rowInfo.original
});
}
};
}}
/>
</ContextMenuTrigger>
{this.state.showContextMenu ? (
<MyAwesomeMenu clickedData={this.state.rowClickedData} />
) : null}
</div>
);
}
}
const MyAwesomeMenu = (props: { clickedData: any }) => (
<ContextMenu id="menu_id">
<MenuItem
data={props.clickedData}
onClick={(e, props) => onClick({ e, props })}
>
<div className="green">ContextMenu Item 1 - {props.clickedData.id}</div>
</MenuItem>
</ContextMenu>
);
const onClick = (props: {
e:
| React.TouchEvent<HTMLDivElement>
| React.MouseEvent<HTMLDivElement, MouseEvent>;
props: Object;
}) => console.log("-------------->", props);
What is the best (and simplest) way to add a context menu to react-table so I can use clicked row's props? I really like react-contextify but haven't found any examples.
Thanks
React Hooks exmaple on dev.to
Class Based Compnent example on codepen
class App extends React.Component {
constructor() {
super();
this.state = {
value: ''
};
}
render() {
return(
<div>
{
['row1', 'row2', 'row3'].map((row) => {
return (
<ContextMenu
key={row}
buttons={[
{ label: 'Editovat', onClick: (e) => alert(`Editace ${row}`) },
{ label: 'Smazat', onClick: (e) => alert(`Mažu ${row}`) }
]}
>
<div className="row">{row}</div>
</ContextMenu>
);
})
}
</div>
);
}
}
class ContextMenu extends React.Component {
static defaultProps = {
buttons: []
};
constructor() {
super();
this.state = {
open: false
};
}
componentDidMount() {
document.addEventListener('click', this.handleClickOutside);
document.addEventListener('contextmenu', this.handleRightClickOutside);
}
handleClickOutside = (e) => {
if (!this.state.open) {
return;
}
const root = ReactDOM.findDOMNode(this.div);
const context = ReactDOM.findDOMNode(this.context);
const isInRow = (!root.contains(e.target) || root.contains(e.target));
const isInContext = !context.contains(e.target);
if (isInRow && isInContext) {
this.setState({
open: false
});
}
}
handleRightClickOutside = (e) => {
if (!this.state.open) {
return;
}
const root = ReactDOM.findDOMNode(this.div);
const isInRow = !root.contains(e.target);
if (isInRow) {
this.setState({
open: false
});
}
}
handleRightClick = (e) => {
e.preventDefault();
console.log(e.nativeEvent, window.scrollY);
this.setState({
open: true,
top: window.scrollY + e.nativeEvent.clientY,
left: e.nativeEvent.clientX,
});
}
render() {
return (
<div
onContextMenu={this.handleRightClick}
ref={(node) => this.div = node}
>
{this.props.children}
{
!this.state.open
? null
: <div
className="context"
ref={(div) => this.context = div}
style={{ top: this.state.top, left: this.state.left }}
>
<ul>
{
// button - name, onClick, label
this.props.buttons.length > 0 &&
this.props.buttons.map((button) => {
return <li key={button.label}>
<a href="#" onClick={button.onClick}>
{button.label}
</a>
</li>
})
}
</ul>
</div>
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));

How i can reload current page in react?

Here I'm fetching data with ApiClient and after that I make operation to confirm to delete the current item. My question here is how i can reload the page after I click Yes ?
type Props = {
resourceName: string,
label: string,
accessor: string,
row: Object,
}
#withRouter
#connectAPI((apiClient: ApiClient, props: Props) => ({
deleteObject: {
send: (id: any) => apiClient.resources[props.resourceName].delete(id),
success: () => {
// TODO: ?
},
},
}))
class DeleteComponent extends React.Component<Props> {
state = {
open: false,
};
handleOpen = () => {
this.setState({ open: true });
};
handleConfirm = (props: Props) => {
const { accessor, row, deleteObject } = props;
const id = row._original[accessor];
deleteObject({
id,
});
};
render(): React.ReactNode {
const { open } = this.state;
let message = null;
if (open) {
message = (
<MessageDialog
message="Are tou sure you want to delete ?"
actions={{
yes: {
label: 'Yes',
callback: this.handleConfirm,
},
no: {
label: 'No',
callback: (e: SyntheticMouseEvent<HTMLButtonElement>) => this.setState({ open: false }),
},
}}
/>
);
}
return (
<React.Fragment>
<Button
position="right"
onClick={this.handleOpen}
hoverIndicator="light-1"
/>
{message}
</React.Fragment>
);
}
}
export default DeleteComponent;

Resources