React js to refresh page after onClick has been called - reactjs

I've been following one of the MERN stack tutorials online (making a simple todo app), and decided to go off-script a little bit. I wanted to add a button to delete a specific item. The delete function is working fine, however it requires the user to manually refresh the page after they click the delete button in order to see the new list of elements in my database (MongoDB). I'd like the page to automatically refresh after the click event, however I'm not sure where to start. Within the react render there is a table, which references a variable to actually assemble the components of the table - this is where the delete button exists. Here is my code:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
const Todo = props => (
<tr>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_title}</td>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_description}</td>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_responsible}</td>
<td className={props.todo.todo_completed ? 'completed' : ''}>{props.todo.todo_priority}</td>
<td>
<Link to={"/edit/"+props.todo._id}>Edit</Link>
</td>
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(console.log("Deleted: " + props.todo._id))
.catch(err => console.log(err))
}
>Delete</button>
</td>
</tr>
)
export default class TodosList extends Component {
constructor(props) {
super(props);
this.state = {todos: []};
}
componentDidMount() {
axios.get('http://localhost:4000/todos/')
.then(res => {
this.setState({ todos: res.data });
})
.catch(function(err){
console.log(err);
})
}
todoList() {
return this.state.todos.map(function(currentTodo, i){
return <Todo todo={currentTodo} key={i} />;
})
}
render() {
return (
<div>
<h3>Todos List</h3>
<table className="table table-striped" style={{ marginTop: 20 }} >
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Responsible</th>
<th>Priority</th>
<th>Action</th>
<th>Remove Item</th>
</tr>
</thead>
<tbody>
{ this.todoList() }
</tbody>
</table>
</div>
)
}
}
Hopefully someone on here can get me pointed in the right direction.
Thanks

You can delete the specific item from TodosList component state after you have successfully deleted the item from Todo component. For that you can
1) add a method in TodosList component.
deleteItemHandler = (id) => {
const updatedTodos = this.state.todos.filter(todo => todo.id !== id);
this.setState({todos: updatedTodos})
}
2) pass the method deleteItemHandler as props to Todo component
todoList() {
return this.state.todos.map((currentTodo, i) => {
return <Todo todo={currentTodo} deleteItem={this.deleteItemHandler} key={i} />;
})
}
3) use it after item is successfully deleted
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(() => props.deleteItem(props.todo._id))
.catch(err => console.log(err))
}
>Delete</button>
</td>
Another way
Instead deleting item from TodosList component you can also update the state. For that you can
1) add method that updates in TodosList component
updateStateHandler = () => {
axios.get('http://localhost:4000/todos/')
.then(res => {
this.setState({ todos: res.data });
})
.catch(function(err){
console.log(err);
})
}
2) pass the method updateStateHandler as props to Todo component
todoList() {
return this.state.todos.map((currentTodo, i) => {
return <Todo todo={currentTodo} updateState={this.updateStateHandler} key={i} />;
})
}
3) use it after item is successfully deleted
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(() => props.updateState())
.catch(err => console.log(err))
}
>Delete</button>
</td>

You need to do this
export default class TodosList extends Component {
constructor(props) {
super(props);
this.state = {todos: []};
this.fetchTodos = this.fetchTodos.bind(this);
}
fetchTodos() {
axios.get('http://localhost:4000/todos/')
.then(res => {
this.setState({ todos: res.data });
})
.catch(function(err){
console.log(err);
});
}
componentDidMount() {
this.fetchTodos();
}
todoList() {
return this.state.todos.map((currentTodo, i) => {
return <Todo todo={currentTodo} fetchTodos={this.fetchTodos} key={i} />;
})
}
...
Todo:
<td>
{/* this is where the delete happens */}
<button onClick={ () =>
axios.delete('http://localhost:4000/todos/'+props.todo._id)
.then(() => {
console.log("Deleted: " + props.todo._id);
props.fetchTodos();
})
.catch(err => console.log(err));
}
>Delete</button>
</td>

The authority that renders the table is your TodosList class, so it needs to be told to do the deleting:
class TodosList extends ... {
...
todoList() {
return this.state.todos.map((currentTodo, i) => {
let onDelete = () => {
this.removeItem(i);
};
// NEVER use an array position as key. The key is meant to uniquely
// identify the _item itself_ and is used in DOM diffing. Moving elements
// inside an array does not change those elements in the slightest and only
// requires moving DOM nodes around, but if you use array position as key,
// what you've now done is said that _everything in the DOM node has changed_
// So: don't do that. Use a real, item based, value.
return <Todo todo={currentTodo} key={currentTodo.id} onDelete={onDelete}/>;
// Of course this assumes todo items have an `id` property.
// If they don't, pick another property _on the todo item_ that
// uniquely identifies it.
});
}
removeItem(i) {
let todos = this.state.todos;
todos.splice(i,1);
// This single call now results in all the UI updates that you
// need to have happen: the todo item is no longer in the state,
// and so its DOM node will be removed from the page. And because
// we're now using real keys, React will not touch any of the other
// DOM nodes. The UI update is near-instant.
this.setState({ todos });
}
...
}
Then the individual buttons can call their own onDelete once deleting has happened:
const deleteThisItem = () => {
axios
.delete('http://localhost:4000/todos/'+props.todo._id)
.then(this.props.onDelete())
.catch(err => console.log(err))
};
<button onClick={deleteThisItem}>delete</button>
So the flow is:
TodoList knows all the todo items,
TodoList generates UI for each todo item,
item UI includes a button that will call TodoList's item deletion function, which will update the TodoList state
simply by virtue of removing an item from that state and calling setState with that change, React will render what needs to be rendered.

Related

How to save changes of a input in table row as single and multiple?

I have some inputs in every row(PreviewItemRow.js) of Table component. I get the data from Redux store. I keep PreviewItemRow changes as internal state. There is also a save button in every button whose onClick event makes an api call to server.
Problem is I want user to save(make api call) his changes as batch requests and also use should be able to save as individual row.
If I reflect changes directly to redux store changes state in redux whenever user presses a button in keyboard, I wont be able to be sure if changes reflected to server.
If I keep the name as component internal state, I can not track changes from SaveAll button.
So how can I Implement to save changes from a button individual row and a button in parent component ?
Parent Table Component
const ParentTableComp = (props) => {
const cases = useSelector(store => store.taskAppReducer.Case.cases);
const handleSaveAllClick = () => {
dispatch(previewBulkSave({
taskId: selectedTask.taskId,
caseData: cases.map(item => ({
name: item.caseName,
}))
}))
.then(() => {
saveSuccess("All saved.");
})
.catch((err) => {
saveError(err);
});
};
return (
<div>
<Button
type='button'
color='primary'
onClick={handleSaveAllClick}
>
Save All
</Button>
<Table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{cases.map((item, index) => (
<tr key={item.caseId}>
<PreviewCaseItem
case={item}
/>
</tr>
))}
</tbody>
</Table>
</div>
);
};
This is the Row component.
const PreviewItemRow = (props) => {
const [name, setName] = useState(props.case.name)
const dispatch = useDispatch();
const handleSaveButtonClick = () => {
dispatch(saveCase({
taskType: taskType,
updatedCase: {
...props.case,
name
},
}))
.then(() => {
saveSuccess("Case Updated");
})
.catch((err) => {
saveError(err);
});
};
const handleNameChange = (event) => {
setName(event.target.value)
}
return (
<div>
<td style={{ width: 100 }}>
<Input
type={"text"}
id={`name-${props.case.caseId}`}
value={name}
onChange={handleNameChange}
/>
</td>
</div>
);
};

React refresh the page after delete button

The delete function of my app is working fine, however it requires the user to manually refresh the page after the user click the delete button in order to see the new list of elements in my database. I would like to automatically refresh after the click event. I am using react hooks for this projects. However, I found one solution if I remove useEffect's [] but in my backend it shows, its requesting crazily. I don't know, is it wise to remove useffect's [ ]?
Here is the component where it fetches data from backend and passes the props to another component
import React, { useState, useEffect } from "react";
import axios from "axios";
import Table from "../Table/Table";
import "./Display.css";
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const [searchItem, setsearchItem] = useState({
item: ""
});
const Search = e => {
setsearchItem({ item: e.target.value });
};
useEffect(() => {
axios
.get("/students")
.then(response => {
setState({
students: response.data.students,
count: response.data.count
});
})
.catch(function(error) {
console.log(error);
});
}, []); //If I remove this square brackets, it works
const nameFilter = state.students.filter(list => {
return list.name.toLowerCase().includes(searchItem.item.toLowerCase());
});
return (
<div>
<h3 align="center">Student tables</h3>
<p align="center">Total students: {state.count}</p>
<div className="input-body">
<div className="row">
<div className="input-field col s6">
<input placeholder="search student" onChange={Search} />
</div>
</div>
</div>
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Date of birth</th>
<th>Address</th>
<th>Zipcode</th>
<th>City</th>
<th>Phone</th>
<th>Email</th>
<th colSpan="2">Action</th>
</tr>
</thead>
{nameFilter.map((object, index) => {
return (
<tbody key={index}>
<Table obj={object} /> //In here I am passing the props to the another component.
</tbody>
);
})}
</table>
</div>
);
};
export default Display;
This is second component which receives the props.
import React, { useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(console.log("Deleted"))
.catch(err => console.log(err));
};
return (
<React.Fragment>
<tr>
<td>{props.obj.name}</td>
<td>{props.obj.birthday}</td>
<td>{props.obj.address}</td>
<td>{props.obj.zipcode}</td>
<td>{props.obj.city}</td>
<td>{props.obj.phone}</td>
<td>{props.obj.email}</td>
<td>
<Link
to={"/edit/" + props.obj.id}
className="waves-effect waves-light btn"
>
Edit
</Link>
</td>
<td>
<button onClick={removeData} className="waves-effect red btn ">
Remove
</button>
</td>
</tr>
</React.Fragment>
);
};
export default Table;
The [] in the useEffect hook is a dependency array to trigger the effect to run. If you want to trigger the effect (without it going off mercilessly), you can create a new variable that triggers that effect to run.
import React, { useState, useEffect } from "react";
import axios from "axios";
import Table from "../Table/Table";
import "./Display.css";
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const [requestData, setRequestData] = useState(new Date());
const [searchItem, setsearchItem] = useState({
item: ""
});
const Search = e => {
setsearchItem({ item: e.target.value });
};
useEffect(() => {
axios
.get("/students")
.then(response => {
setState({
students: response.data.students,
count: response.data.count
});
})
.catch(function(error) {
console.log(error);
});
}, [requestData]);
const nameFilter = state.students.filter(list => {
return list.name.toLowerCase().includes(searchItem.item.toLowerCase());
});
return (
<div>
<h3 align="center">Student tables</h3>
<p align="center">Total students: {state.count}</p>
<div className="input-body">
<div className="row">
<div className="input-field col s6">
<input placeholder="search student" onChange={Search} />
</div>
</div>
</div>
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Date of birth</th>
<th>Address</th>
<th>Zipcode</th>
<th>City</th>
<th>Phone</th>
<th>Email</th>
<th colSpan="2">Action</th>
</tr>
</thead>
{nameFilter.map((object, index) => {
return (
<tbody key={index}>
<Table obj={object} setRequestData={setRequestData} />
</tbody>
);
})}
</table>
</div>
);
};
export default Display;
Then you can trigger it from your Table component:
import React, { useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(() => {
props.setRequestData(new Date());
})
.catch(err => console.log(err));
};
return (
<React.Fragment>
<tr>
<td>{props.obj.name}</td>
<td>{props.obj.birthday}</td>
<td>{props.obj.address}</td>
<td>{props.obj.zipcode}</td>
<td>{props.obj.city}</td>
<td>{props.obj.phone}</td>
<td>{props.obj.email}</td>
<td>
<Link
to={"/edit/" + props.obj.id}
className="waves-effect waves-light btn"
>
Edit
</Link>
</td>
<td>
<button onClick={removeData} className="waves-effect red btn ">
Remove
</button>
</td>
</tr>
</React.Fragment>
);
};
export default Table;
Not sure if helps but you can always remove the item from the current array, so a refresh is not needed, for example you can pass as props a function that receives an id and then filter the students array to exclude the element that matches with that id and then update the state with the new array and count properties, something like this
In your parent:
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const deleteItem = (id) => {
const newStudents = state.students.filter(student => student.id !== id)
const newCount = newStudents.length;
setState({ students: newStudents, count: newCount })
}
// Rest of the code
}
Now pass that function to your child component.
<Table obj={object} deleteItem={deleteItem} />
In the child component just modify your removeData method to add the deleteItem prop:
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(console.log("Deleted"))
.catch(err => console.log(err));
// Now if your request succeeds call the function to remove the item from the students state array
props.deleteItem(props.obj.id);
};
// Rest of the code
}
I know this does not answer your question, but when you're working with react or it is better to do this computations and filters on the app side, like in this case that even though the record was deleted from the db we also removed the record from the student state object and there's no need to refresh the page.
Remember, you're creating a single page application, so we want the nicest experience for the user without refreshing the page for every action the user makes.
Have a look at this
import React, { useState } from "react";
const Display = () => {
const [refresh, setRefresh] = useState(false)
const delete=() => {
// ................. //delete logic
reload ? setRefresh(false) : setRefresh(true) //toggle just to change state
}
useEffect(() => {
}, [reload]); //inject as dependency
}

setState called from within functional argument fails to cause render

*edit to provide solution in comments
I have an app that renders 2 components, a SearchBar form and a Table of data. After the app mounts, an api call is made and setState is called, which triggers a render of the Table. It works fine.
The problem comes from the SearchBar component. On submission, the functional argument handleSubmit is called to make an api request and set the new state. SetState should trigger a render but it doesn't. The state is verified and accurate but there is no render.
Here is my code.
App.js
class App extends Component {
constructor(props) {
console.log('app constructor')
super(props)
this.state = {
items: [],
}
}
render() {
console.log('app render')
return (
<div>
<SearchBar onSubmit={this.handleSubmit} />
<Table data={this.state.items} />
</div>
)
}
componentDidMount() {
console.log('app mounted')
fetch('/api/items/?search=initial search')
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
console.log('state post mount ' + this.state.items.length)
})
}
handleSubmit(e) {
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
console.log('state post submit ' + this.state.items[0].name)
})
}
}
SearchBar.js
export default class SearchBar extends Component {
constructor(props) {
console.log('search bar constructor')
super(props)
this.onChange = this.handleChange.bind(this)
this.onSubmit = this.props.onSubmit.bind(this)
this.state = {
value: ''
}
}
handleChange(e) {
console.log('search bar changed ' + e.target.value)
this.setState({
searchBarValue: e.target.value
})
}
render() {
return (
<form className='form' id='searchForm' onSubmit={this.onSubmit}>
<input type='text' className='input' id='searchBar' placeholder='Item, Boss, or Zone' onChange={this.onChange} />
</form>
)
}
}
Table.js
export default class Table extends Component {
render() {
if (this.props.data.length === 0) {
return (
<p>Nothing to show</p>
)
} else {
return (
<div className="column">
<h2 className="subtitle">
Showing <strong>{this.props.data.length} items</strong>
</h2>
<table className="table is-striped">
<thead>
<tr>
{Object.entries(this.props.data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{this.props.data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</table>
</div>
)
}
}
}
Please set this in a variable, when function initiate:-
handleSubmit(e) {
let formthis=this;
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
formthis.setState({
items: data
})
console.log('state post submit ' + formthis.state.items[0].name)
})
}
AS I said in the comment, Remove this line this.onSubmit = this.props.onSubmit.bind(this) from the SearchBar component and replace this one
<form className='form' id='searchForm' onSubmit={this.onSubmit}>
with
<form className='form' id='searchForm' onSubmit={this.props.onSubmit}>
The problem is when you call bind the onSubmit from the props with the this as you did it is using the context of the SearchBar and not the parent so it sets the response to the state of the Search bar and not the App component which you want that way your items state of the parent component never changes an as such you don't get a re-render
Here is the relevant code for my solution. As harisu suggested, I changed the declaration of the form component. I also added a bind statement in the constructor of the parent.
App.js
class App extends Component {
constructor(props) {
console.log('app constructor')
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.state = {
items: [],
}
}
handleSubmit(e) {
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
})
console.log('state post submit ' + this.state.items[0].name)
}
}
SearchBar.js
export default class SearchBar extends Component {
render() {
return (
<form className='form' id='searchForm' onSubmit={this.props.onSubmit}>
<input type='text' className='input' id='searchBar' placeholder='Item, Boss, or Zone' onChange={this.onChange} />
</form>
)
}
}

ReactJs does not generate data into view after calling api

I just found out about reactjs, I do not understand why my code is not error but it can not render the data to view.
I tried the test function to display. it works normally,
But in the getAllProducts function, after calling the api, it seems impossible to update the html code on my page.
What was wrong with me?
Here is my code:
import React from 'react';
class ListObject extends React.Component {
getAllProducts() {
fetch("http://5bd054ce142d360013a172f3.mockapi.io/api/products")
.then(res => res.json())
.then((result) => {
// return (<h1>Why not display????</h1>);
result.map(
(product, i) => {
return <TableRow key={i} data={product} />
}
)
},
(error) => {
return "errrr";
}
)
}
test() {
return (<h1>Hello World</h1>);
}
render() {
return (
<div className="container-fluid">
<table className="table table-hover">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Avatar</th>
<th>Created At</th>
</tr>
</thead>
<tbody>
{this.getAllProducts()}
</tbody>
</table>
{this.test()}
</div>
);
};
}
class TableRow extends React.Component {
render() {
return (
<tr>
<td>{this.props.data.id}</td>
<td>{this.props.data.name}</td>
<td>{this.props.data.avatar}</td>
<td>{this.props.data.createdAt}</td>
</tr>
);
};
}
export default ListObject
You seem to have got it all wrong. In React, you need to render data based on component's state and props. So, you must do something like this:
class YourComponent extends React.Component {
getAllProducts() {
// you can handle a "loading" state as well
this.setState({isLoading: true});
fetch("http://example.com/api/products")
.then(res => res.json())
.then(
(result) => {
this.setState({
products: result,
isLoading: false,
});
},
(error) => {
return this.setState({hasError: true, error})
}
);
}
}
componentDidMount() {
fetchAllProducts();
}
render() {
const {products, isLoading, hasError} = this.state;
if (hasError) {
return (
<p>Something bad happened</p>
);
}
if (isLoading) {
return (
<p>Hey, we're fetching data...</p>
);
}
return (
<table>
{products.map(p => <TableRow ... />)
</table>
)
}
}
NOTE: I've used a few concepts that you should know about, so here are some docs:
Read about componentDidMount() here
We can declare special methods on the component class to run some code when a component mounts and unmounts. The componentDidMount() method runs after the component output has been rendered to the DOM.
Read about state here
Change your getAllProducts and add state object to the component like mentioned below. API call is asynchronous so you cannot return it directly. what you can do is use component state. And make the api call in componentDidMount to get the api data.
class ListObject extends React.Component {
state = {
result: []
};
componentDidMount() {
this.getAllProducts();
}
getAllProducts() {
return fetch("https://5bd054ce142d360013a172f3.mockapi.io/api/products")
.then(res => res.json())
.then(result => {
// return (<h1>Why not display????</h1>);
this.setState({
result
});
})
.catch(e => {
//dispatch some action to showcase error or
//make state update using setState to show error
return null;
});
}
getProductListUI = () => {
const { result } = this.state;
return result.map((product, i) => <TableRow key={i} data={product} />);
};
test() {
return <h1>Hello World</h1>;
}
render() {
return (
<div className="container-fluid">
<table className="table table-hover">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Avatar</th>
<th>Created At</th>
</tr>
</thead>
<tbody>{this.getProductListUI()}</tbody>
</table>
{this.test()}
</div>
);
}
}
class TableRow extends React.Component {
render() {
return (
<tr>
<td>{this.props.data.id}</td>
<td>{this.props.data.name}</td>
<td>{this.props.data.avatar}</td>
<td>{this.props.data.createdAt}</td>
</tr>
);
}
}
ReactDOM.render(<ListObject />, document.getElementById("root"));
Here is the codepen link working : working codepen link
Feedbacks welcome Thanks

React - require some aid with saving data to a table

I have a table with dynamically generated column headers and rows, a user can enter in data to the rows and when they click save the data should be saved into that row but at the moment it isn't saving the values to the table but it is saving them on my database. Ideally I would like it so that when the save button is clicked the data will be saved to the row and is then viewable in that row (if that makes any sense).
Here is the code I am working with (I know it is a mess at the moment!):
Code for data input form:
import React from 'react';
import AppStore from '../../stores/AppStore';
export default class RowForm extends React.Component {
state = {dataEntries: []};
onChange = (event, element) => {
let dataEntries = this.state.dataEntries;
dataEntries[element] = event.target.value;
this.setState({dataEntries});
};
editStop = () => {
this.props.editStop();
};
handleSubmit = (e) => {
e.preventDefault();
let access_token = AppStore.getToken();
let id = AppStore.getTable().id;
let dataEntries = this.state.dataEntries;
let dataEntriesArray = [];
for (let key in dataEntries) {
if (dataEntries.hasOwnProperty(key)) {
dataEntriesArray.push({contents: dataEntries[key]});
}
}
this.props.handleSubmit(access_token, id, dataEntriesArray);
};
componentDidMount() {
let nameArray = AppStore.getTable().columns.map((obj) => {
return obj.name;
});
let dataEntries = nameArray.reduce((obj, name) => {
obj[name] = null;
return obj;
}, {});
this.setState({dataEntries});
}
render() {
let {dataEntries} = this.state;
return (
<tr>
{Object.keys(dataEntries).map((element) => {
return (
<td key={element}><input type="text" className="form-control" id={element} placeholder="enter data" value={dataEntries[element]} onChange={event => this.onChange(event, element)} /></td>
);
})}
<td>
<button className="btn btn-default" onClick={this.editStop}><i className="fa fa-ban"></i>Cancel</button>
<button className="btn btn-success" onClick={this.handleSubmit}><i className="fa fa-check"></i>Save</button>
</td>
</tr>
);
}
After the data has been entered and submitted (it is an array of objects like dataEntriesArray = [{contents: "value"}, {contents: "value"}, {contents: "value"}, {contents: "value"}].
And here is how I am rendering the table (this is where the problem is I think):
import React from 'react';
import TableHeader from './TableHeader.jsx';
import RowForm from './RowForm.jsx';
import {createRow} from '../../actions/DALIActions';
import AppStore from '../../stores/AppStore';
export default class Table extends React.Component {
state = {rows: [], isNew: false, isEditing: false};
handleAddRowClickEvent = () => {
let rows = this.state.rows;
rows.push({isNew: true});
this.setState({rows: rows, isEditing: false});
};
handleEdit = (row) => {
this.setState({isEditing: true});
};
editStop = () => {
this.setState({isEditing: false});
};
handleSubmit = (access_token, id, dataEntriesArray) => {
createRow(access_token, id, dataEntriesArray);
};
render() {
let {rows, isNew, isEditing} = this.state;
let headerArray = AppStore.getTable().columns;
return (
<div>
<div className="row" id="table-row">
<table className="table table-striped">
<thead>
<TableHeader />
</thead>
<tbody>
{rows.map((row, index) => this.state.isEditing ?
<RowForm formKey={index} key={index} editStop={this.editStop} handleSubmit={this.handleSubmit} /> :
<tr key={index}>
{headerArray.map((element, index) => {
return (
<td key={index} id={element.id}></td>
);
})}
<td>
<button className="btn btn-primary" onClick={this.handleEdit.bind(this, row)}><i className="fa fa-pencil"></i>Edit</button>
</td>
</tr>)}
</tbody>
</table>
</div>
<div className="row">
<div className="col-xs-12 de-button">
<button type="button" className="btn btn-success" onClick={this.handleAddRowClickEvent}>Add Row</button>
</div>
</div>
</div>
);
}
}
I am using flux at the moment, and would ideally like to keep using it for now (I know about redux but I would ideally like to get it working in flux before I start refactoring my code). I suspect it is a problem with the way I am rendering my table.
Any help would be much appreciated, especially examples!
Thanks for you time!
It looks like you probably want to extract your table data into your store, your UI child elements trigger change events, your store then updates its data and trigger change events which your parent component can listen for and update.
Something like the following simplified example, which mutates array elements:
class Store extends EventEmitter {
constructor() {
super()
this.data = [ 'a', 'b', 'c' ]
}
onChange() {
this.emit( 'update', this.data )
}
mutate( index, value ) {
this.data[ index ] = value
this.onChange()
}
}
var store = new Store()
class ChildComponent extends React.Component {
constructor( props ) {
super( props )
}
// You probably want to use a dispatcher rather than directly accessing the store
onClick = event => {
store.mutate( this.props.index, this.props.value + 'Z' )
}
render() {
return <button onClick={ this.onClick }>{ this.props.value }</button>
}
}
class ParentComponent extends React.Component {
constructor( props ) {
super( props )
// You probably want to be smarter about initially populating state
this.state = {
data: store.data
}
}
componentWillMount() {
store.on( 'update', data => this.setState({ data: data }) )
}
render() {
let cells = this.state.data.map( ( value, index ) => <ChildComponent index={ index } value={ value } /> )
return (
<div>
{ cells }
</div>
)
}
}
For brevity child components here directly tell the store to change values, you'd probably want to dispatch messages/actions and have the store decide how to respond, the key is simply that the store data is passed to the parent component, which updates its state and triggers a re-render.
The flow here is that essentially the UI is dumb, it simply renders the data that it gathers from the store and dispatches a message to tell the store to update/mutate when a user action is detected (in this case a button press but it sound like you'll need an input of some sort), when the data in the store changes it emits (or could use a dispatcher also) a change event which forces the UI to re-render the new state. As child components are re-rendered at this stage they are populated with the new data state, ensuring that your UI remains consistent.

Resources