In Material-UI's website Table page, there's one specific table that I was interested in using for a project, labeled material-table (https://material-ui.com/components/tables/#material-table). It's got a search bar, a button for adding new rows, and the possibility of editing or deleting existing rows, also through buttons. In the site, everything works perfectly, but the source code provided doesn't, and I'm not sure why.
import React from 'react';
import MaterialTable from 'material-table';
export default function MaterialTableDemo() {
const [state, setState] = React.useState({
columns: [
{ title: 'Name', field: 'name' },
{ title: 'Surname', field: 'surname' },
{ title: 'Birth Year', field: 'birthYear', type: 'numeric' },
{
title: 'Birth Place',
field: 'birthCity',
lookup: { 34: 'İstanbul', 63: 'Şanlıurfa' },
},
],
data: [
{ name: 'Mehmet', surname: 'Baran', birthYear: 1987, birthCity: 63 },
{
name: 'Zerya Betül',
surname: 'Baran',
birthYear: 2017,
birthCity: 34,
},
],
});
return (
<MaterialTable
title="Editable Example"
columns={state.columns}
data={state.data}
editable={{
onRowAdd: newData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
setState(prevState => {
const data = [...prevState.data];
data.push(newData);
return { ...prevState, data };
});
}, 600);
}),
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
if (oldData) {
setState(prevState => {
const data = [...prevState.data];
data[data.indexOf(oldData)] = newData;
return { ...prevState, data };
});
}
}, 600);
}),
onRowDelete: oldData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
setState(prevState => {
const data = [...prevState.data];
data.splice(data.indexOf(oldData), 1);
return { ...prevState, data };
});
}, 600);
}),
}}
/>
);
}
The code above is the one shown on the site. When I try to use it, however, while the Edit button allows me to change the fields of the selected row, the changes are not saved when I use the Save button.
Also, the Delete one seems to always delete the last row of the table, even if it wasn't the one I chose to delete. Adding new rows, however, seems to work normally.
How can I fix this?
const data = [...prevState.data];
data[data.indexOf(oldData)] = newData;
I changed it into:
const foundIndex = oldData.findIndex(x => x.name === newData.name)
data[foundIndex] = newData
return data
It isn't pretty and I don't get all the setTimeouts, but here are working edit and delete on the current demo.js in the material-table on GitHub as of today (the reason I found this question in the first place, because edit and delete don't work on that code either). NOTE: This is scoped to this and does not use prevState style usage of setState, but leaving this here as a working solution for demo.js code.
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
if (oldData) {
const data = [...this.state.data];
const foundIndex = data.findIndex(x => x.name === newData.name)
data[foundIndex] = newData
this.setState({...this.state, data});
}
}, 600);
}),
onRowDelete: (oldData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
const data = [...this.state.data];
const foundIndex = data.findIndex(x => x.name === oldData.name)
data.splice(foundIndex, 1);
this.setState({...this.state, data});
}, 1000);
}),
Related
Hi so I'm trying to grab some json from an api and then populate a table, pretty simple stuff.
What's happening is that I can see the "tableData" state being updated as each new row comes in, I'm also logging every time "tableData" is updated, yet maybe .5 seconds after its all done my "tableData" is empty again (check console screenshots)
const [bigChartData, setbigChartData] = React.useState("data1");
const [tableData, setTableData] = React.useState([]);
const setBgChartData = (name) => {
setbigChartData(name);
};
const getData = () => {
axios.get("URL")
.then(res => {
const data = res.data.items.forEach(item => {
setTableData(oldData => [...oldData, {
data: [
{ text: item.title },
{ text: "asd" + item.url },
{ text: "some links..." }
]
}]);
});
})
.catch(err => console.log(err));
setTimeout(function () {
console.log(tableData);
}, 3000);
}
useEffect(() => {
getData();
}, []);
useEffect(() => {
console.log("Table data updated:");
console.log(tableData);
}, [tableData]);
I think you should not iterate through each row inside getData() method instead try following code
const getData = () => {
axios.get("URL")
.then(res => {
const data = res.data.items.map(item => {
return{
data: [
{ text: item.title },
{ text: "asd" + item.url },
{ text: "some links..." }
]
};
});
setTableData(data)
}).catch(err => console.log(err));
}
or if you have already some data in tableData then
setTableData([...tableData, data])
First time using react and firebase real-time database. I'm having a hard time extracting the data and inserting it into const items. From my understanding, the firebase code is asynchronous, so the code will execute after the page is loaded up.
After checking the console numerous times, I see the data loads into const tasks array. However, the data inside tasks[] doesn't display when I put it inside the items array(title, subtitle, etc). I've looked at multiple solutions on this and none of them have answered my issue.
I thought it was because of the items being a const, but after changing it to var, the issue persisted. Any sort of guidance would be appreciated here.
One small note: using await doesn't seem to work for me whatsoever. I could be mixing things up due to my lack of knowledge of firebase + react.
const Timelines = () => {
const [tasks, setTasks] = useState([]);
useEffect(() => {
const ref = firebase.database().ref();
const listener = ref.on('value', snapshot => {
const fetchedTasks = [];
snapshot.forEach(childSnapshot => {
const key = childSnapshot.key; //using await here doesn't work. Return 'await is a reserved word error'
const data = childSnapshot.val(); //using await here doesn't work. Return 'await is a reserved word error'
fetchedTasks.push({ id: key, ...data });
});
setTasks(fetchedTasks);
});
return () => ref.off('value', listener);
}, [firebase.database()]);
const items = [{
title: "Header" + tasks,
cardTitle: "Title",
//url: "http://www.history.com",
cardSubtitle: "LA",
cardDetailedText: ["Paragrah"],
media: {
type: "IMAGE",
source: {
url: "./images/image1.jpeg"
}
}
},
{
title: "Title",
cardTitle: "Title1",
//url: "http://www.history.com",
cardSubtitle: "LA",
cardDetailedText: "Paragraph 1",
media: {
type: "IMAGE",
source: {
url: "./images/img-2.jpg"
}
}
},
]
return (
<div className='timeline' style={{ width: "auto", height: "auto" }}>
<Chrono items={items}
slideShow
mode="VERTICAL_ALTERNATING"
theme={{
primary: "#7e8083",
secondary: "white",
cardBgColor: "#00467f",
cardForeColor: "white",
titleColor: "#00467f"
}}
useReadMore="true" />
</div>
)
};
export default Timelines;
Your problem may be rerendering problem. So you can try loading state. Check my useEffect and return.
const Timelines = () => {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(false); //<----
useEffect(() => {
setLoading(true) //<-----
const ref = firebase.database().ref();
const listener = ref.on('value', snapshot => {
const fetchedTasks = [];
snapshot.forEach(childSnapshot => {
const key = childSnapshot.key; //using await here doesn't work. Return 'await is a reserved word error'
const data = childSnapshot.val(); //using await here doesn't work. Return 'await is a reserved word error'
fetchedTasks.push({ id: key, ...data });
});
setTasks(fetchedTasks);
setLoading(false) //<---
});
return () => ref.off('value', listener);
}, [firebase.database()]);
const items = [{
title: "Header" + tasks,
cardTitle: "Title",
//url: "http://www.history.com",
cardSubtitle: "LA",
cardDetailedText: ["Paragrah"],
media: {
type: "IMAGE",
source: {
url: "./images/image1.jpeg"
}
}
},
{
title: "Title",
cardTitle: "Title1",
//url: "http://www.history.com",
cardSubtitle: "LA",
cardDetailedText: "Paragraph 1",
media: {
type: "IMAGE",
source: {
url: "./images/img-2.jpg"
}
}
},
]
return !loading && (
<div className='timeline' style={{ width: "auto", height: "auto" }}>
<Chrono items={items}
slideShow
mode="VERTICAL_ALTERNATING"
theme={{
primary: "#7e8083",
secondary: "white",
cardBgColor: "#00467f",
cardForeColor: "white",
titleColor: "#00467f"
}}
useReadMore="true" />
</div>
)
};
export default Timelines;
Thank you
What Gökhan Geyik stated as a solution is correct. But I had to adjust my code a little since my Firebase db structure was different. The problem was, rerendering!
useEffect(() => {
setLoading(true)
const ref = firebase.database().ref('Overview/events');
const listener = ref.on('value', snapshot => {
const fetchedTasks = [];
const data = snapshot.val();
fetchedTasks.push(data);
setTasks(fetchedTasks);
setLoading(false)
});
return () => ref.off('value', listener);
}, [firebase.database()]);
return !loading && (
....
)
This worked like a charm. Much kudos to Gökhan Geyik for the right direction!
This is my test code snippet but it throws an exception, TypeError: componentInstance.loadLoanApplication is not a function :
it('should render the SubmittedLoan', () => {
const loanData = {
data: {
id: 1,
};
const div = document.createElement('div');
const wrapper = mount(
<AppProviders>
<MemoryRouter initialEntries={['/review/153']}>
<SubmittedLoan
match={{ params: { loanId: 1, step: 1 } }}
history={{
location: { state: { from: 'register' } },
push() {},
}}
/>
</MemoryRouter>
</AppProviders>,
div,
);
const componentInstance = wrapper
.find(SubmittedLoan)
.children()
.first()
.children()
.first()
.instance();
const loanApplication = {
id: 1,
steps_data: [
{ slug: 'step_1', title: 'Step 1' },
{ slug: 'step_2', title: 'Step 2' },
],
status: ApiCaptiq.STATUS_SUBMITTED,
};
expect(wrapper.find(SubmittedLoan).length).toBe(1);
componentInstance.loadLoanApplication(1, 1);
componentInstance.onLoadLoanApplication(loanData);
componentInstance.onLoadFail();
componentInstance.setState({
formData: [{ item: 'value' }, { item2: 'value2' }],
activeStep: 1,
loanApplication,
});
componentInstance.handleSnackbarClose(new Event('click'), '');
componentInstance.setState({ activeStep: 3 });
});
Then my Component which uses memo is as follows :
export const SubmittedLoan = memo(() => {
const [loanApplication, setLoanApplication] = useState<LoanApplication | null>(null);
const [message, setMessage] = useState({
message: '',
open: false,
messageType: '',
});
const authContext = useContext(AuthContext);
const customerContext = useCustomerData();
const params = useParams();
const history = useHistory();
const classes = useStyles();
const { loanId } = params;
const onLoadFail = useCallback(() => {
setMessage({
message: 'Die verfügbaren Darlehensarten können nicht aufgelistet werden',
open: true,
messageType: 'error',
});
}, []);
const onLoadLoanApplication = useCallback(
(response: AxiosResponse) => {
setTemplateSettings(response, authContext);
if (
response.data.status === ApiCaptiq.STATUS_STARTING ||
response.data.status === ApiCaptiq.STATUS_IN_PROGRESS ||
response.data.status === ApiCaptiq.STATUS_PRE_WAITING
) {
history.push(`/view/${loanId}`);
} else {
setLoanApplication(response.data);
}
},
[loanId, authContext, history],
);
const loadLoanApplication = useCallback(
async (loan_id: number) => {
try {
const response = await request.get(`${ApiCaptiq.LOAN_APPLICATION_URL}${loan_id}/`);
const { fetchCustomerProfile } = customerContext;
await fetchCustomerProfile(response.data.customer_profile_id);
onLoadLoanApplication(response);
} catch (err) {
onLoadFail();
}
},
[customerContext, onLoadLoanApplication, onLoadFail],
);
...
What could be the possible reason for this
The functions you are defining inside the component, are not just available on the component instance. In fact, there is not way to call them. You can test only by mocking the fetch calls they are doing.
If you really need callable functions in your component (you should try to avoid these..), you could use this: https://reactjs.org/docs/hooks-reference.html#useimperativehandle
Perhaps better would be to extract this data loading logic elsewhere and test it separately.
I'm trying to push new data to my posts array and display it on the client with DOM manipulation. The 1st two elements get displayed as expected but the 3rd element is not getting displayed while I'm using the async/await keyword
const posts = [
{
title: 'Post One',
body: 'This is post one'
},
{
title: 'Post Two',
body: 'This is post two'
}
];
const getPosts = () => {
setTimeout(() => {
let output = "";
posts.forEach((post, index) => {
output +=
`<ul>
<li>${post.title}</li>
</ul>`
})
document.getElementById('heading').innerHTML = output;
}, 1000);
}
const createPosts = (newPost) => {
setTimeout(() => {
posts.push(newPost);
}, 2000)
}
const newPost = async () => {
try{
await createPosts({title: 'Post Three', body: 'This is post three'});
getPosts();
}
catch{err => console.log(err)}
}
newPost();
createPosts() needs to return a Promise that's resolved after the timeout, so you can await it.
const createPosts = newPost =>
new Promise(resolve => setTimeout(() => {
posts.push(newPost);
resolve();
}), 2000));
I am trying to fetch and map an array with 350 object's elements. I decided to use Hook and useEffect, to re render my dataTable component since mapping is done. Unfortunately, the whole process takes enormous amount of time, and it makes page unresponsive. After 1-2 minutes, table shows up and after few seconds it disappears. After that page is still unresponsive. Could someone explain why it happens, and give me some workaround? I would be grateful.
Code below:
const Employees = (props) => {
const [developers, setDevelopers] = useState([]);
useEffect(() => {
fetchData();
});
const columns = [
{
name: "Emloyee",
selector: "name",
sortable: true,
},
{
name: "Team ",
selector: "team",
sortable: true,
},
{
name: "Email ",
selector: "email",
sortable: true,
},
];
const fetchData = () => {
axios.get("http://localhost:3128/employees", {
headers: {
'Access-Control-Allow-Origin': '*',
}
})
.then((response) => {
mapData(response.data.developers);
console.log("I am here!");
})
.catch((e) => console.log(e));
};
const mapData = (jsonData) => {
jsonData.forEach((x) => {
let newDeveloper = {
name: x.userId,
team: x.team,
email: x.userId + "#mail.com",
};
setDevelopers((developers) => [...developers, newDeveloper]);
});
};
return <DataTable title="Employees" columns={columns} data={developers}/>;
};
useEffect without dependency array will run on every render, so in your case, you are stuck in an infinite loop which cause page to become unresponsive
solution:
const fetchData = useCallback(() => {
axios.get("http://localhost:3128/employees", {
headers: {
'Access-Control-Allow-Origin': '*',
}
})
.then((response) => {
mapData(response.data.developers);
console.log("I am here!");
})
.catch((e) => console.log(e));
},[]);
const mapData = useCallback((jsonData) => {
jsonData.forEach((x) => {
let newDeveloper = {
name: x.userId,
team: x.team,
email: x.userId + "#kuehne-nagel.com",
};
setDevelopers((developers) => [...developers, newDeveloper]);
});
},[]);
useEffect(() => {
fetchData();
},[fetchData]); // pass dependency array here in useEffect
Thanks to #DrewReese and #SarthakAggarwal , I've got a solution:
const Employees = (props) => {
const [developers, setDevelopers] = useState([]);
const columns = [
{
name: "Emloyee",
selector: "name",
sortable: true,
},
{
name: "Team ",
selector: "team",
sortable: true,
},
{
name: "Email ",
selector: "email",
sortable: true,
},
];
const fetchData = useCallback(() => {
axios.get("http://localhost:3128/employees", {
headers: {
'Access-Control-Allow-Origin': '*',
}
})
.then((response) => {
mapData(response.data.developers);
console.log("I am here!");
})
.catch((e) => console.log(e));
}, []);
const mapData = (jsonData) => {
let table = [];
jsonData.forEach((x) => {
let newDeveloper = {
name: x.userId,
team: x.team,
email: x.userId + "#mail.com",
};
table = [...table,newDeveloper];
//setDevelopers((developers) => [...developers, newDeveloper]);
});
setDevelopers((developers) => table);
};
useEffect(() => {
fetchData();
}, [fetchData]);
return <DataTable title="Employees" columns={columns} data={developers}/>;
};
Thanks a lot !