Trying to add the ability for, when a user adds an item to the table, to edit the values once they hit the edit button.
Code under Edit Project below in my code isn't working. I've tried to research a bunch of different ways on how to do this, but I keep coming up short.
Any idea on how I can do this easily?
Link to codesandbox.
MainCrud.js
import React, { useState } from "react";
import CrudIntro from "../crud/crudIntro/crudIntro";
import CrudAdd from "../crud/crudAdd/crudAdd";
import CrudTable from "../crud/crudTable/crudTable";
const MainCrud = props => {
// Initiazle project data
const projectData = [];
// Initial state for edit button's hidden state to be false
const [editBtn, setEditBtn] = useState(false);
// Initialize state of the project data above to 'projects'
const [projects, setProject] = useState(projectData);
// Add Project
const addProject = project => {
// Create new item with a unique ID
project.id = projects.length + 1;
// Take the current list, and add onto it via the spread operator
setProject([...projects, project]);
};
// Delete Project
const deleteProject = id => {
setProject(projects.filter(project => project.id !== id));
};
// Edit Project
const editProject = e => {
setEditBtn(true);
this.setState({
...this.state.name,
...this.state.description,
...this.state.date,
[e.target.name]: e.target.value
});
};
return (
<div>
<section id="add">
<CrudIntro title={props.title} subTitle={props.subTitle} />
<CrudAdd addProject={addProject} />
</section>
<section id="main">
<CrudTable
projectData={projects}
editProject={editProject}
deleteProject={deleteProject}
/>
</section>
</div>
);
};
export default MainCrud;
I see you are trying to do this.setState in a functional component, which you can't do. this will point to undefined in this case
Related
I have developing a react app about blog sites. But at individual blog's like state affecting others. When i increase the like, other blog's likes increases too. :( I am using localstorage within useEffect but when page reloads, like count returns to initial state -> 10, localstorage keeps increased like count : 11 and when refresh -> goes to initial like : 10. In short state is updating at client side but not at localstorage and at page refresh it turns back to initial state.
import { useState } from "react";
import { useParams } from "react-router";
import useFetch from "../useFetch";
// FIXME: state changes spreads all of elements
const BlogDetails = () => {
const { id } = useParams();
const { data: blog } = useFetch(`http://localhost:8000/blogs/${id}`)
const [likes, setLikes] = useState(10);
const handleClick = (e) => {
localStorage.setItem('likes', likes);
setLikes(likes => likes + 1);
e.target.setAttribute('disabled', true)
}
return (
<div className="blog-details">
<h3><b>{blog.title}</b></h3>
<p>{blog.content}</p>
<small><em><p>Author: {blog.author}</p></em></small>
<small><p className="likes">Likes: {localStorage.getItem('likes')}</p></small>
<button onClick={handleClick}>Like</button>
</div>
);
}
export default BlogDetails;
This happens because even though the blog IDs are different, all of your blog posts use the SAME BlogDetails component. Therefore, all the blog posts actually share the same state. One solution would be, instead of having likes being a simple number, set up likes to be an object, with specific blog's id mapping to the that individual blog's like count. Or you can store the blog post's like count into the database also
const allBlogIds = ["dragon", "mountain", "pikachu"]; // You add more depending on your blog
const initialLikes = {};
// Set up `initialLikes`
allBlogIds.forEach((id) => {
initialLikes[id] = 0;
});
// Now `intialLikes` will look like {"dragon": 0, "mountain": 0, "pikachu": 0}
const BlogDetails = (props) => {
const { id } = useParams();
const { data: blog } = useFetch(`http://localhost:8000/blogs/${id}`);
const [likes, setLikes] = useState(initialLikes); // `likes` is an object
// Get the like count of this specific blog post
const likeCount = likes[id];
const handleClick = (e) => {
setLikes((old) => ({ ...old, id: old[id] + 1 }));
};
return (
<div>
<p>Blog post's specific like count: {likeCount}</p>
<button onClick={handleClick}>increase count for this blog post only</button>
</div>
);
};
Keeping state like this is quite cumbersome as you can see, so it is recommended that you save the like count data to your database (not local storage) also, and query/fetch these data whenever needed
, Using props I was able to effectively pass state upwards from my child component to its parent, but a change in the state does not cause a re-render of the page.
import React, { useState } from "react";
export default function App() {
const AddToList = (item) => {
setText([...text, item]);
};
const removeFromList = (item) => {
const index = text.indexOf(item);
setText(text.splice(index, 1));
};
const [text, setText] = React.useState(["default", "default1", "default2"]);
return (
<div className="App">
<div>
<button
onClick={() => {
AddToList("hello");
}}
>
Add
</button>
</div>
{text.map((item) => {
return <ChildComponent text={item} removeText={removeFromList} />;
})}
</div>
);
}
const ChildComponent = ({ text, removeText }) => {
return (
<div>
<p>{text}</p>
<button
onClick={() => {
removeText(text);
}}
>
Remove
</button>
</div>
);
};
In the snippet, each time AddToList is called, a new child component is created and the page is re-rendered reflecting that. However, when i call removeFromList on the child component, nothing happens. The page stays the same, even though I thought this would reduce the number of childComponents present on the page. This is the problem I'm facing.
Updated Answer (Following Edits To Original Question)
In light of your edits, the problem is that you are mutating and passing the original array in state back into itself-- React sees that it is receiving a reference to the same object, and does not update. Instead, spread text into a new duplicate array, splice the duplicate array, and pass that into setText:
const removeFromList = (item) => {
const index = text.indexOf(item);
const dupeArray = [...text];
dupeArray.splice(index, 1);
setText(dupeArray);
};
You can see this working in this fiddle
Original Answer
The reason React has things like state hooks is that you leverage them in order to plug into and trigger the React lifecycle. Your problem does not actually have anything to do with a child attempting to update state at a parent. It is that while your AddToList function is properly leveraging React state management:
const AddToList = (item) => {
setText([...text, item]);
};
Your removeFromList function does not use any state hooks:
const removeFromList = (item) => {
const index = text.indexOf(item);
text.splice(index, 1); // you never actually setText...
};
...so React has no idea that state has updated. You should rewrite it as something like:
const removeFromList = (item) => {
const index = text.indexOf(item);
const newList = text.splice(index, 1);
setText(newList);
};
(Also, for what it is worth, you are being a little loose with styles-- your AddToList is all caps using PascalCase while removeFromCase is using camelCase. Typically in JS we reserve PascalCase for classes, and in React we also might leverage it for components and services; we generally would not use it for a method or a variable.)
I am using a table to display data. I would like to get the row data into a div when the user clicks the table row:
Currently I am getting the data onclick which is working:
const handleClick = (rowData) => {
console.log(rowData);
}
Although currently not sure how I should pass and change the data into the div, here's the full code:
const Start = ({rowData}) => {
const options = {
onRowClick: rowData => handleClick(rowData),
}; // MUI Datatables onRowClick, works fine
const handleClick = (rowData) => {
console.log(rowData); // shows data in console, works
}
return (
<div className="tablehead">
<h2>({rowData})</h2> // no data here, how to pass the onclick data here?
</div>
)
}
export default Start;
So what's happened here is that you have mixed up passing row data props with a variable that is the same name. Instead you need to look into managing state through hooks.
const Start = ({rowData}) => {
const [dataForDisplay, setDataForDisplay] = useState(rowData);
const handleClick = (rowData) => {
setDataForDisplay(rowData);
}
return (
<div className="tablehead">
<h2>({dataForDisplay})</h2>
</div>
)
}
export default Start;
However using props to seed state is most of the time an anti-pattern, so you might want to reconsider your component hierarchy. Either way however you'll need to look into state management and hooks.
https://reactjs.org/docs/state-and-lifecycle.html
I am trying to have a user be able to click an item from a list of all possible items and have a modal open to display data about that item (including the current quantity they have) and buttons to increment/decrement that amount.
To my understanding since I am just showing data that is being passed in and then dispatching an action to update the store I should be using a functional component to display the data and useDispatch to call the store action.
Currently when I update the store I see the change in Redux debugging tools but the change is not reflected in the modal until I reopen it. While I have been looking for answers to this I see many similar questions but they all use Class Components and mapStateToProps (such as this post). I thought best practices was to use functional components unless needed. Am I wrong to think that if I am getting a value from the store in a functional component it should update on change?
Code Snippets
Dialog
export default function ItemDialog({
...
selectedItem,
}) {
const dispatch = useDispatch()
const inventory = useSelector(
state => state.user.inventory
)
let userItem = inventory.find(
userItem => userItem.name === selectedItem.name
)
const changeItemCount = (item, change) => {
item.change = change
dispatch({
type: "USER_INVENTORY_UPDATED",
payload: item
})
}
const showQuantity = userItem => {
return userItem.quantity > 0 ? `(${userItem.quantity})` : ""
}
...
render(
<p className="text-xl text-center font-semibold">
{selectedItem.name}
</p>
<p className="text-center font-light">
{showQuantity(userItem)}
</p>
...
<AddBoxIcon
onClick={() => changeItemCount(selectedItem, 1)}
/>
)
Store
const userReducer = (state = InitialUserState, action) => {
let inventoryCopy = { ...state.inventory }
switch (action.type) {
case "USER_INVENTORY_UPDATED":
let category = action.payload.category
let updatedItemIndex = inventoryCopy[category].findIndex(
item => item.name === action.payload.name.toUpperCase()
)
// If item is already there
if (updatedItemIndex >= 0) {
inventoryCopy[category][updatedItemIndex].quantity +=
action.payload.change
} else {
// If item needs to be added to inventory category
let newItem = {
name: action.payload.name,
quantity: action.payload.change
}
inventoryCopy[category].push(newItem)
}
return {
...state,
inventory: inventoryCopy
}
...
default:
return state
}
}
Check your spread operator when you return your updated state. You may need to deep clone the old state depending on how many nested objects it has.
The docs have more information on shallow cloning objects.
Deeply cloning your state object will help you get rid of:
let inventoryCopy = { ...state.inventory }
I am new to react js and I am trying to create a pivot table using React Pivot table. I want to be able to select a data set using a select drop down menu and update the state and re render the full table whenever there is a change in the drop down selection just like the jquery example shown here https://pivottable.js.org/examples/rcsvs.html
It works before I make any selections or changes to the Pivot Table. I am able to toggle between the 2 datasets and the state changes in the Pivot table. But when I select a pivot dimension and use the pivot table, after that point, changing the select menu does not help me change the pivot table's state. Please help.
Here's my code.
import React from 'react';
import PivotTableUI from 'react-pivottable/PivotTableUI';
import 'react-pivottable/pivottable.css';
import TableRenderers from 'react-pivottable/TableRenderers';
import Plot from 'react-plotly.js';
import createPlotlyRenderers from 'react-pivottable/PlotlyRenderers';
// create Plotly renderers via dependency injection
const PlotlyRenderers = createPlotlyRenderers(Plot);
const data1 = [{'Country':'USA','Sales':45000},
{'Country':'USA','Sales':50000},{'Country':'CA','Sales':15000}]
const data2 = [{'Product':'Sofa','Sales':5000},{'Product':'Dinner
Table','Sales':50000},{'Product':'Chair','Sales':15000}]
const dataDic = {'Region':data1,'Products':data2}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {selectedOption: 'Region'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({selectedOption: event.target.value});
}
handleSubmit(event) {
alert('You have selected ' + this.state.selectedOption);
event.preventDefault();
}
render() {
return <div>
<select defaultValue="Region" onChange={(e)=>
this.handleChange(e)}>
<option value="Region">Region</option>
<option value="Products">Products</option>
</select>
<br/>
<br/>
<PivotTableUI
data={dataDic[this.state.selectedOption]}
onChange={s => this.setState(s)}
renderers={Object.assign({},TableRenderers)}//,PlotlyRenderers)}
{...this.state}
/>
</div>;
}
}
export default App;
I found that if I delete the data property from s
<PivotTableUI
data = {[{}]}
onChange={ s =>{
delete s.data
this.setState(s)
}}
{...this.state}
/>
This won't overwrite the data in the parent class and will be automatically rendered with updated data
Working code sandbox
There were two problems;
s => this.setState(s) in your PivotTable's onChange property.
This overrides root state with all the props of your PivotTable.
When your page initiated, container's (Grid) state only contains selectedOption:"Region" but after interacting with the PivotTable, container has receives all the props of the PivotTable. Screenshot:
{...this.state} prop in PivotTableUI component, passes all keys as props in container's state including data.(As seen in the screenshot above). And this overrides data property, data={dataDic[this.state.selectedOption]} After this, changes to selectedOption state does not re-render PivotTableUI
Solution
Change s => this.setState(s) with this.setState({ pivotTableUIConfig: s });
Define a pivotTableUIConfig variable which does not include data property. (Used ES7 Object Rest Operator to Omit data property)
// Picking all the properties except "data"
const { data, ...pivotTableUIConfig } = this.state.pivotTableUIConfig;
Change {...this.state} with {...pivotTableUIConfig}
Encountered the same problem and found a simple fix. The problem is that data prop always gets overwritten by {...state}. Hence we can assign data to state before passing it to PivotTableUI. And the data prop is no longer needed.
const state = {...this.state};
state['data'] = dataDic[this.state.selectedOption];
return (
<PivotTableUI
onChange={s => this.setState(s)}
{...state}
/>
)
you should add
renderers={{ ...TableRenderers, ...PlotlyRenderers }}
{...state}
to your PivotTableUI, see the full example:
import TableRenderers from 'react-pivottable/TableRenderers';
import PivotTableUI from 'react-pivottable/PivotTableUI';
import Plot from 'react-plotly.js';
import createPlotlyRenderers from 'react-pivottable/PlotlyRenderers';
import 'react-pivottable/pivottable.css';
const PlotlyRenderers = createPlotlyRenderers(Plot);
function DataExploration() {
const { dashboardData: { data } } = useSelector((state) => state.dashboardData)
const ExpComp = (props) => {
const [state, setState] = useState(props);
return (
<div className="row">
<div className="col-md-12">
{
<PivotTableUI
style={
{
width: "100%",
height: "100%"
}
}
onChange={(s) => setState(s)}
renderers={{ ...TableRenderers, ...PlotlyRenderers }}
{...state}
/>
}
</div>
</div>
)
}
return (
<ExpComp data={data} />
)
}
From more details check the doc: https://github.com/plotly/react-pivottable