have this code snippet for a budget input and display. global state is updated in context and then repopulated in my display here "currentList" through component did mount.
the API updates but the page and state does not rerender. should i be using useEffect on the handle submit? when i try it tells me my dispatch is lost
state = {
expenses: []
}
componentDidMount() {
API.getExpense().then((expenses) => {
console.log(expenses.data);
this.setState({ expenses: expenses.data });
});
}
handleSubmit = (dispatch) => {
dispatch({
type: 'remove',
expenses: this.state.expenses,
})
this.setState({
expenses: [
...this.state.expenses
]
});
console.log(this.state.expenses)
};
deleteExpense = (id) => {
API.deleteExpense(id)
};
currentList = () => {
const currentList =
this.state.expenses.length > 0 ? (
this.state.expenses.map((expense, index) => {
const { dispatch } = this.state;
return (
<tr key={index}>
<td>{expense.expenseTitle}</td>
<td>{expense.amount}</td>
<td>{expense.category}</td>
<td>
<span className="delete-btn" role="button" id={expense._id} tabIndex="0" onClick={(e) => { this.deleteExpense(e.currentTarget.id) }} onClick={this.handleSubmit.bind(this, dispatch)}>
✗
</span>
</td>
</tr>
);
})
) : (
<tr></tr>
);
return currentList;
}
render() {
return (
<div className='card mt-5' >
<table className='table-bordered'>
<thead>
<tr>
<th>title</th>
<th>amount</th>
<th>category</th>
<th>remove</th>
</tr>
</thead>
<BudgetConsumer>
{(value) => {
// console.log(value.expenses);
const expensesList =
value.expenses.length > 0 ? (
value.expenses.map((expense, index) => {
const { dispatch } = this.state;
return (
<tr key={index}>
<td>{expense.expenseTitle}</td>
<td>{expense.amount}</td>
<td>{expense.category}</td>
<td>
<span className="delete-btn" role="button" id={expense._id} tabIndex="0" onClick={(e) => { this.deleteExpense(e.currentTarget.id) }} onClick={this.handleSubmit.bind(this, dispatch)}>
✗
</span>
</td>
</tr>
);
})
) : (
<tr></tr>
);
return <tbody>{this.currentList()}{expensesList}</tbody>;
}}
</BudgetConsumer>
<tbody></tbody>
</table>
</div >
);
}
}
try this in the componentDidMount
this.setState({expenses: [...expenses.data]});
fixed, was thinkng too hard into the
componentDidMount() {
console.log(this.state.expenses)
API.getExpense().then((expenses) => {
console.log(expenses.data);
this.setState({ expenses: expenses.data });
});
}
// call delete for the API then rerun the get expense API to reset the state
deleteExpense = (id) => {
API.deleteExpense(id).then(
API.getExpense().then((expenses) => {
console.log(expenses.data);
this.setState({ expenses: expenses.data });
})
)
};
Related
I'm trying to render a table of data fetch from an API.
The first render is fine, everything seems to be ok, but when i'm trying to use SortBy or when i'm trying to add an input that i could use to call filtered data to the API i got the error :
react-dom.development.js:27292 Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
at checkForNestedUpdates (react-dom.development.js:27292:1)
at scheduleUpdateOnFiber (react-dom.development.js:25475:1)
at dispatchReducerAction (react-dom.development.js:17452:1)
at react-table.development.js:944:1
at react-table.development.js:253:1
at commitHookEffectListMount (react-dom.development.js:23150:1)
at commitLayoutEffectOnFiber (react-dom.development.js:23268:1)
at commitLayoutMountEffects_complete (react-dom.development.js:24688:1)
at commitLayoutEffects_begin (react-dom.development.js:24674:1)
at commitLayoutEffects (react-dom.development.js:24612:1)
const TableAnime = () => {
const [animes, setAnimes] = useState({});
const [loadingData, setLoadingData] = useState(true);
const [endpoint, setEndpoint] = useState('/anime?page[limit]=10&page[offset]=0')
const getAnimes = async () => {
const response = await axios.get(`https://kitsu.io/api/edge${endpoint}`);
setAnimes(response.data);
setLoadingData(false);
}
useEffect(() => {
getAnimes()
}, []);
const handleForm = async () => {
const results = await axios.get(`https://kitsu.io/api/edge/anime`);
setAnimes(results.data)
console.log(animes)
}
const data = useMemo(() => (animes.data), [animes.data]);
return (
<div>
<Form handleForm={handleForm}/>
<h1>Catalogue</h1>
{loadingData ?
(
<p>Loading Please wait...</p>
) : (
<Table animes={animes.data} />
)
}
</div>
);
}
// == Export
export default TableAnime;
Table.js :
// == Import
import { useTable } from "react-table";
import { useMemo } from "react";
import { Link } from 'react-router-dom';
import moment from "moment"
import RowItem from "./RowItem";
import HeaderItem from "./HeaderItem";
// == Composant
const Table = ({animes}) => {
const columns = useMemo(() => [
{ Header: "Titre", accessor: "attributes.canonicalTitle"},
{ Header: "Titre Japonais", accessor: "attributes.titles.ja_jp"},
{ Header: "Age recommandé", accessor: "attributes.ageRatingGuide"},
{ Header: "Date de sortie", accessor: d => moment(d.attributes.startDate).format("DD/MM/YYYY")},
{ Header: "Rang", accessor: "attributes.popularityRank"},
{ Header: " ", accessor: d => <Link to={`/anime/${d.id}`}>Voir les détails</Link>},
]);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({columns, data: animes})
return (
<>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<HeaderItem headerGroup={headerGroup} key={Date.now()}/>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row)
return (
<RowItem key={(Date.now()*Math.random())} row={row} />
)
})}
</tbody>
</table>
</>
);
}
// == Export
export default Table;
Then i got RowItem and HeaderItem :
const RowItem = ({row}) => {
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps() }>
{cell.render('Cell')}
</td>
)
})}
</tr>
);
}
export default RowItem
const HeaderItem = ({headerGroup}) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()} >
{column.render('Header')}
</th>
))}
</tr>
);
export default HeaderItem ;
From what i understand that could be because i didn't use useMemo properly but i don't know how to do that. I tried at multiple places but nothing worked.
I'm using React Table for the first time and i'm new to React, so I'm sorry if the answer seems really easy but i really coudn't figured it out myself.
You need to cache the getAnime fetcher function by using useCallback and pass the endpoint as the dependency to make sure the fetcher fn is only called once (twice in strict mode).
So, change this:
const getAnimes = async () => {
const response = await axios.get(`https://kitsu.io/api/edge${endpoint}`);
setAnimes(response.data);
setLoadingData(false);
}
useEffect(() => {
getAnimes()
}, []);
into this:
const getAnimes = useCallback(async () => {
const response = await axios.get(`https://kitsu.io/api/edge${endpoint}`);
setAnimes(response.data);
setLoadingData(false);
}, [endpoint]);
useEffect(() => {
getAnimes();
}, [getAnimes]);
You can check here
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 months ago.
Improve this question
I am getting this error while populating the data into the table
error image
<template>
<div>
<!-- {{console.log(users)}} -->
<table class="table m-0">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id">
<!-- <td>{{ Country }}</td> -->
<!-- <td>{{ user.id }}</td> -->
<td>alksdmad</td>
<td>
<!-- <router-link :to="`/edit/${id}`"> -->
<button class="btn btn-primary btn-sm me-2">Edit</button>
<!-- </router-link> -->
<button class="btn btn-danger btn-sm">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
// import { useLoadUsers, deleteUser } from '#/firebase'
import { db } from "#/firebase";
import { ref, onMounted } from "vue";
import { collection, getDocs } from "firebase/firestore";
// const users = ref([
// {
// id: doc.id
// }
// ])
export default {
// data() {
// return {
// users: ref([
// ]),
// };
// },
setup() {
const users = ref([])
onMounted( async () => {
const querySnapshot = await getDocs(collection(db, "users"));
let fbUsers = []
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
const user = {
id: doc.id
}
fbUsers.push(user)
});
users.value = fbUsers
})
},
};
</script>
<style>
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
I am using the using the code above to access the db data
alksdlkal da dlk as dlasjdhaskjdaksj dkjashdk jashkjdas ksjd ja hdkaj kjdahs kjd asksj dh akjsh dkjas hdkjah dkjash dkjhaskjd askjhdakj askj hkajsdhkjashd kjashdkjhd kjaskj dakjd askjd
Try to define the users property directly inside the setup hook :
export default{
setup() {
const users = ref([])
onMounted( async () => {
const querySnapshot = await getDocs(collection(db, "users"));
let fbUsers = []
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
const user = {
id: doc.id
}
fbUsers.push(user)
});
users.value = fbUsers
})
return {users}
},
}
I'm trying to create a simple css background transition on the rows of a table as items go from inactive to active.
The transition should fade from blue (active === false) to pink (active === true).
The transition works as expected when going from blue to pink and the order changes, but not when going from pink to blue and the order changes.
I've made a simple example of my problem https://codesandbox.io/s/simple-react-8pf4s
Thanks in advance for any help!
That might be just how React handles list reconciliation.
When the top item is turning inactive, it's first removed from the DOM and then appended as a new child. Which is why there is no transition. The transition works when you're turning the bottom item active as it's again the top item that's getting removed, so the transitioning item stays in the DOM.
Having keys on the table rows in this particular scenario doesn't help since, as far as I can tell from this, they are only used for optimization and do not actually guarantee the same DOM elements will be re-used.
You might stumble across the same problem mentioned here.
You could look into some libraries for transitioning lists to find a potential solution. E.g. react-flip-move
Part of me hopes that I'm wrong cause it kinda sucks and makes a simple thing quite complicated.
I was able to get the sorting issue figured out for you, but the real stumper is the CSS issue..
The issue has to do with keys and creating mapping over items.. From what I make of it, React will apply the transition to a specific key.. After toggling to inactive, it's like React is treating it as a completely new node, and for some reason it doesn't correctly apply the transition..
I am not sure why this works one way but not the other, it's really odd to me... I was unable to find a fix online for this, and I tested a bunch of stuff.. You should look into CSS transitions with React and keys..
With that being said, I did come up with a few "hacky" ways to accomplish this..
CSS AS JS OBJECTS:
const { useState, useEffect } = React;
const { render } = ReactDOM;
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
const sourceData = [
{
id: "es",
1: "uno",
2: "dos",
3: "tres",
active: true
},
{
id: "de",
1: "eine",
2: "zwei",
3: "drei",
active: false
},
{
id: "abc",
1: "a",
2: "b",
3: "c",
active: false
}
];
const activeClass = {
background: "pink",
color: "blue",
transition: "all 1s"
};
const inactiveClass = {
background: "blue",
color: "pink",
transition: "all 1s"
};
const App = () => {
const [data, setData] = useState(sourceData);
useEffect(() => {
setClassNames();
}, [data]);
const sortData = d => d.sort((a, b) => (a[1] < b[1] ? -1 : 1));
const sortAllData = d => [
...sortData(d.filter(i => i.active)),
...sortData(d.filter(i => !i.active))
];
const handleToggle = index => event => {
let clone = [...data];
clone[index].active = !clone[index].active;
setData(sortAllData(clone));
};
const setClassNames = () => {
let actives = document.querySelectorAll(`[dataactive=${true}]`);
let inactives = document.querySelectorAll(`[dataactive=${false}]`);
setTimeout(() => {
actives.forEach(a => {
Object.keys(activeClass).forEach(k => a.style[k] = activeClass[k])
});
inactives.forEach(ina => {
Object.keys(inactiveClass).forEach(k => ina.style[k] = inactiveClass[k])
});
}, 10);
};
return (
<div style={styles}>
<table>
<thead>
<tr>
<td>Active?</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</thead>
<tbody>
{data.map((d, index) => {
return (
<tr dataactive={d.active.toString()} key={d.id} id={d.id}>
<td>{d.active.toString()}</td>
<td>{d[1]}</td>
<td>{d[2]}</td>
<td>{d[3]}</td>
<td>
<button onClick={handleToggle(index)}>TOGGLE</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
};
render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
USING A .CSS FILE:
const { useState, useEffect } = React;
const { render } = ReactDOM;
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
const sourceData = [
{
id: "es",
1: "uno",
2: "dos",
3: "tres",
active: true
},
{
id: "de",
1: "eine",
2: "zwei",
3: "drei",
active: false
},
{
id: "abc",
1: "a",
2: "b",
3: "c",
active: false
}
];
const App = () => {
const [data, setData] = useState(sourceData);
useEffect(() => {
setClassNames();
}, [data]);
const sortData = d => d.sort((a, b) => (a[1] < b[1] ? -1 : 1));
const sortAllData = d => [
...sortData(d.filter(i => i.active)),
...sortData(d.filter(i => !i.active))
];
const handleToggle = index => event => {
let clone = [...data];
clone[index].active = !clone[index].active;
setData(sortAllData(clone));
};
const setClassNames = () => {
let actives = document.querySelectorAll(`[dataactive=${true}]`);
let inactives = document.querySelectorAll(`[dataactive=${false}]`);
setTimeout(() => {
actives.forEach(a => a.className = "active");
inactives.forEach(ina => ina.className = "inactive");
}, 10);
};
return (
<div style={styles}>
<table>
<thead>
<tr>
<td>Active?</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</thead>
<tbody>
{data.map((d, index) => {
return (
<tr dataactive={d.active.toString()} key={d.id} id={d.id}>
<td>{d.active.toString()}</td>
<td>{d[1]}</td>
<td>{d[2]}</td>
<td>{d[3]}</td>
<td>
<button onClick={handleToggle(index)}>TOGGLE</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
};
render(<App />, document.body);
.active {
background: pink;
color: blue;
transition: all 1s;
}
.inactive {
background: blue;
color: pink;
transition: all 1s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
This is strange behaviour, but I assume that as react is controlling inserting the dom node in its new position, the class is already added and so renders immediately. To counteract this I have created a keyframe animation and a stack of ids that are animating to apply this animation to. I could confirm that the same element was being reused (and not destroyed and recreated) by logging refs as they were created.
https://codesandbox.io/s/objective-dijkstra-ifren
const App = () => {
const [animating, setAnimating] = useState([]);
const removeAnimation = id => {
setAnimating(animating.filter(x => x !== id));
};
const [data, setData] = useState(sourceData);
const toggle = id => {
let data2 = [...data];
const index = data.findIndex(x => x.id === id);
data2[index] = { ...data2[index], active: !data2[index].active };
setAnimating([...animating, data2[index].id]);
data2 = [...data2.filter(x => x.active), ...data2.filter(x => !x.active)];
setData(data2);
};
return (
<div className="App">
{data.map(d => (
<div
key={d.id}
className={cl(
"row",
d.active && "row_active",
!d.active && animating.includes(d.id) && "row_hasAnimation",
d.active && animating.includes(d.id) && "row_active_hasAnimation"
)}
onAnimationEnd={() => removeAnimation(d.id)}
>
<div className="cell">{d[1]}</div>
<div className="cell">{d[2]}</div>
<div className="cell">{d[3]}</div>
<div className="cell">
<button onClick={() => toggle(d.id)}>Toggle</button>
</div>
</div>
))}
</div>
);
};
I imported a json file in my file and i created an action like this:
import jsonData from './data/account_list_response.json';
const inAccountsList = () => {
store.dispatch({
type: 'ACCOUNT_LIST',
state: {
accounts: jsonData
}
});
};
Then, i make the accounts as prop in my accountList container like this:
function mapStateToProps(state) {
return { accounts: state.get('accounts').map(account => account.toJS()) };
}
and finally
const AccountList = ({ accounts }) => (
<div className="account-list">
<h4 className="table-header">Accounts</h4>
<Table hover>
<thead>
<tr>
<th>#</th>
<th>Account Number</th>
<th>Description</th>
<th>Balance</th>
<th>Available Balance</th>
<th>Currency</th>
</tr>
</thead>
<tbody>
{accounts.map((account, i) =>
<AccountEntry
key={account.id} idx={i + 1}
{...account}
/>
)}
</tbody>
</Table>
</div>
);
An error occured Invalid prop accounts of type object supplied to AccountList, expected an array. Why is that??
I'm following egghead.io Redux course by Dan.
However I've no idea why my todo app is not working. It doesn't output any error and any warning, just doesn't work.
Can you please give me some hint.
This is my jsbin.
https://jsbin.com/cozecip/33/edit?js,output
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
};
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state;
}
return {
id: state.id,
text: state.text,
completed: !state.completed
};
default:
return state;
}
};
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
case 'TOGGLE_TODO':
return state.map(t =>
todo(t, action)
);
default:
return state;
}
};
const visibilityFilter = (state = 'SHOW_ALL', action) =>
{
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
};
const { combineReducers } = Redux;
const todoApp = combineReducers({
todos,
visibilityFilter
});
const { createStore } = Redux;
const store = createStore(todoApp);
const { Component } = React;
const FilterLink = ({
filter,
currentFilter,
children,
onClick
}) => {
if (filter === currentFilter){
return <span>{children}</span>
}
return (
<a href="#" onClick ={onClick}>{children}</a>
);
};
const Todo = ({
onClick,
completed,
text
}) => (
<li
onClick={onClick}
style={{
textDecoration:
completed ?
'line-through' :
'none'
}}
>
{text}
</li>
);
const TodoList = ({
todos,
onTodoClick
}) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
const AddTodo = ({
onAddClick
}) => {
let input;
return (
<div>
<input ref={node => {
input = node;
}} />
<button onClick={() => {
onAddClick(input.value)
input.value = '';
}}>
Add Todo
</button>
</div>
);
}
const Footer = ({
visibilityFilter,
onFilterClick
}) => (
<p>
Show:
{' '}
<FilterLink
filter='SHOW_ALL'
currentFilter={visibilityFilter}
onClick={onFilterClick}>
All
</FilterLink>
{' '}
<FilterLink
filter='SHOW_ACTIVE'
currentFilter={visibilityFilter}
onClick={onFilterClick}>
Active
</FilterLink>
{' '}
<FilterLink
filter='SHOW_COMPLETED'
currentFilter={visibilityFilter}
onClick={onFilterClick}>
Completed
</FilterLink>
</p>
)
const getVisibleTodos = (todos, filter) => {
switch(filter){
case 'SHOW_ALL':
return todos;
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed);
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed);
default:
return todos;
}
}
let nextTodoId = 0;
const TodoApp = ({
todos,
visibilityFilter
}) => {
return (
<div>
<AddTodo
onAddClick={
text =>
store.dispatch({
type: 'ADD_TODO',
text: this.input.value,
id: nextTodoId++
})
}
/>
<TodoList
todos={
getVisibleTodos(
todos,
visibilityFilter
)
}
onTodoClick={id =>
store.dispatch({
type: 'TOGGLE_TODO',
id
})
}
/>
<Footer
visibilityFilter={visibilityFilter}
onFilterClick={filter =>
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter
})
}
/>
</div>
);
}
const render = () => {
ReactDOM.render(
// Render the TodoApp Component to the <div> with id 'root'
<TodoApp
{...store.getState()}
/>,
document.getElementById('root')
);
};
store.subscribe(render);
render();
Make sure you have added the correct ID in index.js
it should look like this.
document.getElementById('your div id from index.html should go here')