Thanks all in advance.
Going by the screenshot:
enter image description here
I wish to use a dynamic variable (filter) in line 33 but it keeps throwing error. If I use the name properties of the object, Filter_M (eg: "ALL", it works fine. If I use the the values too by using the functions (eg:
(order) => !order.completed
) directly, it works fine.
How can I resolve this?
Note: I am following similar procedure as used in MDN article. See:
Back to the Filter Buttons
A portion of my code is shown below while links to react components are shown at the bottom:
import React from "react";
import FilterButton from "./components/filterButton";
import MyOrder from "./components/MyOrder";
// const ORDER = [
// { cname: 'ki', item: 'liveChicken', quantity: 0, id: "order-0", completed: true },
// { cname: 'fu', item: 'egg', quantity: 0, id: "order-1", completed: false },
// { cname: 'nu', item: 'chicken', quantity: 0, id: "order-2", completed: false}
// ]
// const root = ReactDOM.createRoot(document.getElementById('root'));
// root.render(
// <React.StrictMode>
// <App2 orders={ORDER} />
// </React.StrictMode>
// );
const FILTER_M = {
ALL: () => true,
Active: (order) => !order.completed,
Completed: (order) => order.completed
};
const FILTER_NAMES = Object.keys(FILTER_M);
function App(props) {
const chickenPrice = 5000;
const [filter, setFilter] = useState('All');
const [orders, setOrders] = useState(props.orders)
const orderList = orders
.filter(FILTER_M[filter])
.map((order) => (
<MyOrder
item={order.item}
quantity={order.quantity}
cname={order.cname}
id={order.id}
completed={order.completed}
key={order.id}
/>))
const filterList = FILTER_NAMES.map((cname) => (
<FilterButton
key={cname}
cname={cname}
isPressed={cname === filter}
setFilter={setFilter}
/>
));
}
Code Sandbox
I am expecting to display dynamically, the tasks/orders either active, completed, with all orders shown by default.
I have tried hard-coding it by using the property name or the values and it all worked out fine. But using the named variable in object (FILTER_MAP) bracket notation which is the only way of accessing named variable as name property of an object.
Based on the MDN article you provided this code should work, I can't see the complete picture of your code but anyway this code below is working :
const DATA = [
{ id: 'todo-0', name: 'Eat', completed: true },
{ id: 'todo-1', name: 'Sleep', completed: false },
{ id: 'todo-2', name: 'Repeat', completed: false },
];
const filterBy= {
All: (order) => true,
Active: (order) => !order.completed,
Completed: (order) => order.completed,
};
const DisplayList = () => {
const [filter, setFilter] = useState('All');
const [orders, setOrders] = useState(DATA);
console.log(filterBy[filter]);
const orderList = orders.filter(filterBy[filter]).map(order => (
<li key={order.id} id={order.id}>
<p>{order.name}</p>
<p>{`${order.completed}`}</p>
</li>
));
return (
<>
<button onClick={() => setFilter('Active')}>
view active orders ⬇
</button>
{orderList}
</>
);
};
export default function App() {
return (
<DisplayList />
);
}
a working sample in code sandbox
The problem is here :
const FILTER_MAP = {
ALL: () => true, // HERE (ALL) is all CAPS
Active: (order) => !order.completed,
Completed: (order) => order.completed
};
and your are trying to access (All) a property that doesn't exist in FILTER_MAP
const [filter, setFilter] = useState('All'); // not the same as (ALL) in FILTER_MAP
and that is a good example of why you should use type script, because TS would have helped you find this typo.
Related
I have a React app. There are components rendered from mapped data like the following:
function App () {
const [price, setPrice] = useState(1);
const cardDetails = [
{
id: 1,
title: 'card 1',
setPrice: setPrice
},
{
id: 2,
title: 'card 2',
setPrice: setPrice
}
]
const renderCards = (cardDetails) => {
return (
cardDetails.map((c) => {
<Card cardData={c} />
})
)
};
return (
<>
{renderCards(cardDetails)}
</>
)
}
It's working well. Now I'd like to move the cardDetails data into a JSON file. I defined the following cardDetails.js:
export const cardDetails = [
{
id: 1,
title: 'card 1',
setPrice: setPrice
},
{
id: 2,
title: 'card 2',
setPrice: setPrice
}
]
However, I can't pass function setPrice in the JSON file, any idea what I could do to use the external JSON file?
Since the setPrice function only exists in App, it can't be in the separate file. You asked "How to store a function name in JSON?" and while you could do that (setPrice: "setPrice", and then when mapping the cards replace it with the setPrice function), it doesn't really buy you anything.
But it's simple to have App add it to the cards as it's passing them to the Card component: <Card cardData={{ ...c, setPrice }} /> That uses spread syntax to spread out the object from c into a new object and adds setPrice to the new object.
To avoid creating new objects on every render (which might force Card to re-render unnecessarily, if Card is memoized), we can use useMemo to memoize the array of extended cards like this:
const fullCards = useMemo(() => (
cardDetails.map((card) => ({...card, setPrice}))
), [cardDetails]);
...and then use fullCards for the map.
Full version:
In cardDetails.js:
export const cardDetails = [
{
id: 1,
title: "card 1",
},
{
id: 2,
title: "card 2",
},
];
Your component:
import { cardDetails } from "./cardDetails.js";
function App() {
const [price, setPrice] = useState(1);
const fullCards = useMemo(() => (
cardDetails.map((card) => ({...card, setPrice}))
), [cardDetails]);
const renderCards = (cardDetails) => cardDetails.map((c) => {
<Card key={c.id} cardData={c} />;
});
return renderCards(fullCards);
}
Or simply:
function App() {
const [price, setPrice] = useState(1);
const fullCards = useMemo(() => (
cardDetails.map((card) => ({...card, setPrice}))
), [cardDetails]);
return fullCards.map((c) => {
<Card key={c.id} cardData={c} />;
});
}
Note that I added the key prop to the Card elements. You need a key on elements in arrays; details in the React documentation here.
That would also work just fine if you wanted to store the data in a JSON file like:
[
{
"id:: 1,
"title": "card 1",
},
{
"id": 2,
"title": "card 2",
},
]
...and then load and parse that JSON for use in App.
I have the current state as:
const [data, setData] = useState([
{ id: 1, name: "One", isChecked: false },
{ id: 2, name: "Two", isChecked: true },
{ id: 3, name: "Three", isChecked: false }
]);
I map through the state and display the data in a div and call a onClicked function to toggle the isChecked value on click:
const clickData = index => {
const newDatas = [...data];
newDatas[index].isChecked = !newDatas[index].isChecked;
setData(newDatas);
const newSelected = [...selected];
const temp = datas.filter(isChecked==true) // incomplete code, struggling here.
const temp = datas.isChecked ?
};
I have another empty state called clicked:
const[clicked, setClicked] = setState([]). I want to add all the objected whose isChecked is true from the datas array to this array. How can I do this?
I just add checkBox & onChange event instead of using div & onClick event for your understanding
import React, { useState, useEffect } from "react";
import "./style.css";
export default function App() {
const [data, setData] = useState([
{ id: 1, name: "One", isChecked: false },
{ id: 2, name: "Two", isChecked: true },
{ id: 3, name: "Three", isChecked: false }
]);
const [clicked, setClicked] = useState([]);
const clickData = index => {
let tempData = data.map(res => {
if (res.id !== index) {
return res;
}
res.isChecked = !res.isChecked;
return res;
});
setClicked(tempData.filter(res => res.isChecked));
};
useEffect(() => {
setClicked(data.filter(res => res.isChecked));
}, []);
return (
<div>
{data.map((res, i) => (
<div key={i}>
<input
type="checkbox"
checked={res.isChecked}
key={i}
onChange={() => {
clickData(res.id);
}}
/>
<label>{res.name}</label>
</div>
))}
{clicked.map(({ name }, i) => (
<p key={i}>{name}</p>
))}
</div>
);
}
https://stackblitz.com/edit/react-y4fdzm?file=src/App.js
Supposing you're iterating through your data in a similar fashion:
{data.map((obj, index) => <div key={index} onClick={handleClick}>{obj.name}</div>}
You can add a data attribute where you assign the checked value for that element, so something like this:
{data.map((obj, index) => <div key={index} data-checked={obj.isChecked} data-index={index} onClick={handleClick}>{obj.name}</div>}
From this, you can now update your isClicked state when the handleClick function gets called, as such:
const handleClick = (event) => {
event.preventDefault()
const checked = event.target.getAttribute("data-checked")
const index = event.target.getAttribute("data-index")
// everytime one of the elements get clicked, it gets added to isClicked array state if true
If (checked) {
let tempArr = [ ...isClicked ]
tempArr[index] = checked
setClicked(tempArr)
}
}
That will let you add the items to your array one by one whenever they get clicked, but if you want all your truthy values to be added in a single click, then you simply need to write your handleClick as followed:
const handleClick = (event) => {
event.preventDefault()
// filter data objects selecting only the ones with isChecked property on true
setClicked(data.filter(obj => obj.isChecked))
}
My apologies in case the indentation is a bit off as I've been typing from the phone. Hope this helps!
I'm playing around with the use of constructor functions in a React application. However, I have never seen anyone discuss this usage before, so I'm wondering if there's a problem with it that I'm not aware of.
I have a constructor function called NameSection that is serving like a React hook. It has its own state (first and last name of a person) that it manages. I use it in my PersonalInfo component to render one of two things: either the current first/last name or inputs for the two fields so that they may be edited.
One reason I'm using a constructor function is because I'm considering turning it into an interface that can be inherited from. Then, I can later implement a PasswordSection, EmailSection, etc that need to follow a similar structure.
From my tests so far, I haven't seen any issues with using a constructor function in this particular context. But I'd like to hear others' thoughts on this. Are there any problems with this sort of implementation in the world of React?
In PersonalInfo.js
const PersonalInfo = ({info}) => {
const nameSection = new NameSection({firstName: info.firstName, lastName: info.lastName});
return (
<SettingsChild heading={"Name"} onSaveAttempt={nameSection.nameExitEdit} onEdit={nameSection.nameEnterEdit} disableSave={nameSection.isNameSectionError()}>
{nameSection.renderNameSection()}
</SettingsChild>
)
}
export default PersonalInfo;
In NameSection.js
function NameSection({firstName, lastName}) {
const initialNameSection = {
editMode: false,
firstName: {input: firstName, error: ""},
lastName: {input: lastName, error: ""}
}
const [ data, setData ] = useState(initialNameSection);
const handleNameChange = (event) => {
const key = event.target.name;
const value = event.target.value;
setData(prev => ({
...prev,
[key]: {input: value, error: ""}
}))
}
this.isNameSectionError = () => (!data.firstName.input || !data.lastName.input);
this.nameEnterEdit = () => {
setData(prev => ({
...prev,
editMode: true
}))
}
this.nameExitEdit = () => {
if (this.isNameSectionError()) {
if (!data.firstName.input) {
setData(prev => ({
...prev,
firstName: {...prev.firstName, error: inputErrors.requiredField}
}))
}
if (!data.lastName.input) {
setData(prev => ({
...prev,
lastName: {...prev.lastName, error: inputErrors.requiredField}
}))
}
}
else {
setData(prev => ({
...prev,
editMode: false
}))
}
}
this.renderNameSection = () => {
return (
data.editMode ?
<React.Fragment>
<TextBox label={"First name"} name={"firstName"} value={data.firstName.input} onChange={handleNameChange} errorMessage={data.firstName.error}/>
<TextBox label={"Last name"} name={"lastName"} value={data.lastName.input} onChange={handleNameChange} errorMessage={data.lastName.error}/>
</React.Fragment>
:
<p>{`${data.firstName.input} ${data.lastName.input}`}</p>
)
}
}
export default NameSection;
When a user adds additional information, a mutation is made to the database adding the new info, then the local state is updated, adding the new information to the lead.
My mutation and state seem to get updated fine, the issue seems to be that the state of the Material Table component does not match its 'data' prop. I can see in the React Dev tools that the state was updated in the parent component and is being passes down, the table just seems to be using stale data until I manually refresh the page.
I will attach images of the React Devtools as well as some code snippets. Any help would be much appreciated.
Devtools Material Table data prop:
Devtools Material Table State
Material Table Parent Component:
const Leads = () => {
const [leadState, setLeadState] = useState({});
const [userLeadsLoaded, setUserLeadsLoaded] = React.useState(false);
const [userLeads, setUserLeads] = React.useState([]);
const { isAuthenticated, user, loading } = useAuth()
const [
createLead,
{ data,
// loading: mutationLoading,
error: mutationError },
] = useMutation(GQL_MUTATION_CREATE_LEAD);
const params = { id: isAuthenticated ? user.id : null };
const {
loading: apolloLoading,
error: apolloError,
data: apolloData,
} = useQuery(GQL_QUERY_ALL_LEADS, {
variables: params,
});
useEffect(() => {
if (apolloData) {
if (!userLeadsLoaded) {
const { leads } = apolloData;
const editable = leads.map(o => ({ ...o }));
setUserLeads(editable);
setUserLeadsLoaded(true);
};
}
}, [apolloData])
if (apolloLoading) {
return (
<>
<CircularProgress variant="indeterminate" />
</>
);
};
if (apolloError) {
console.log(apolloError)
//TODO: Do something with the error, ie default user?
return (
<div>
<div>Oh no, there was a problem. Try refreshing the app.</div>
<pre>{apolloError.message}</pre>
</div>
);
};
return (
<>
<Layout leadState={leadState} setLeads={setUserLeads} leads={userLeads} setLeadState={setLeadState} createLead={createLead}>
{apolloLoading ? (<CircularProgress variant="indeterminate" />) : (<LeadsTable leads={userLeads} setLeads={setUserLeads} />)}
</Layout>
</>
)
}
export default Leads
Handle Submit function for adding additional information:
const handleSubmit = async (event) => {
event.preventDefault();
const updatedLead = {
id: leadState.id,
first_name: leadState.firstName,
last_name: leadState.lastName,
email_one: leadState.email,
address_one: leadState.addressOne,
address_two: leadState.addressTwo,
city: leadState.city,
state_abbr: leadState.state,
zip: leadState.zipCode,
phone_cell: leadState.phone,
suffix: suffix,
address_verified: true
}
const { data } = await updateLead({
variables: updatedLead,
refetchQueries: [{ query: GQL_QUERY_GET_USERS_LEADS, variables: { id: user.id } }]
})
const newLeads = updateIndexById(leads, data.updateLead)
console.log('New leads before setLeads: ', newLeads)
setLeads(newLeads)
// setSelectedRow(data.updateLead)
handleClose()
};
Material Table Component:
const columnDetails = [
{ title: 'First Name', field: 'first_name' },
{ title: 'Last Name', field: 'last_name' },
{ title: 'Phone Cell', field: 'phone_cell' },
{ title: 'Email', field: 'email_one' },
{ title: 'Stage', field: 'stage', lookup: { New: 'New', Working: 'Working', Converted: 'Converted' } },
{ title: 'Active', field: 'active', lookup: { Active: 'Active' } },
];
const LeadsTable = ({ leads, setLeads }) => {
const classes = useStyles();
const { user } = useAuth();
const [isLeadDrawerOpen, setIsLeadDrawerOpen] = React.useState(false);
const [selectedRow, setSelectedRow] = React.useState({});
const columns = React.useMemo(() => columnDetails);
const handleClose = () => {
setIsLeadDrawerOpen(!isLeadDrawerOpen);
}
console.log('All leads from leads table render: ', leads)
return (
<>
<MaterialTable
title='Leads'
columns={columns}
data={leads}
icons={tableIcons}
options={{
exportButton: false,
hover: true,
pageSize: 10,
pageSizeOptions: [10, 20, 30, 50, 100],
}}
onRowClick={(event, row) => {
console.log('Selected Row:', row)
setSelectedRow(row);
setIsLeadDrawerOpen(true);
}}
style={{
padding: 20,
}}
/>
<Drawer
variant="temporary"
open={isLeadDrawerOpen}
anchor="right"
onClose={handleClose}
className={classes.drawer}
>
<LeadDrawer onCancel={handleClose} lead={selectedRow} setLeads={setLeads} setSelectedRow={setSelectedRow} leads={leads} />
</Drawer>
</>
);
};
export default LeadsTable;
Try creating an object that contains refetchQueries and awaitRefetchQueries: true. Pass that object to useMutation hook as a 2nd parameter. See example below:
const [
createLead,
{ data,
loading: mutationLoading,
error: mutationError },
] = useMutation(GQL_MUTATION_CREATE_LEAD, {
refetchQueries: [{ query: GQL_QUERY_GET_USERS_LEADS, variables: { id: user.id } }],
awaitRefetchQueries: true,
});
Manually updating cache. Example blow is adding a new todo. In your case you can find and update the record before writing the query.
const updateCache = (cache, {data}) => {
// Fetch the todos from the cache
const existingTodos = cache.readQuery({
query: GET_MY_TODOS
});
// Add the new todo to the cache (or find and update an existing record here)
const newTodo = data.insert_todos.returning[0];
cache.writeQuery({
query: GET_MY_TODOS,
data: {todos: [newTodo, ...existingTodos.todos]}
});
};
const [addTodo] = useMutation(ADD_TODO, {update: updateCache});
I'm having some trouble with the React useState hook. I have a todolist with a checkbox button and I want to update the 'done' property to 'true' that has the same id as the id of the 'clicked' checkbox button. If I console.log my 'toggleDone' function it returns the right id. But I have no idea how I can update the right property.
The current state:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: false
},
]
})
const toggleDone = (id) => {
console.log(id);
}
return (
<div className="App">
<Todos todos={state.todos} toggleDone={toggleDone}/>
</div>
);
}
The updated state I want:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: true // if I checked this checkbox.
},
]
})
You can safely use javascript's array map functionality since that will not modify existing state, which react does not like, and it returns a new array. The process is to loop over the state's array and find the correct id. Update the done boolean. Then set state with the updated list.
const toggleDone = (id) => {
console.log(id);
// loop over the todos list and find the provided id.
let updatedList = state.todos.map(item =>
{
if (item.id == id){
return {...item, done: !item.done}; //gets everything that was already in item, and updates "done"
}
return item; // else return unmodified item
});
setState({todos: updatedList}); // set state to new object with updated list
}
Edit: updated the code to toggle item.done instead of setting it to true.
You need to use the spread operator like so:
const toggleDone = (id) => {
let newState = [...state];
newState[index].done = true;
setState(newState])
}
D. Smith's answer is great, but could be refactored to be made more declarative like so..
const toggleDone = (id) => {
console.log(id);
setState(state => {
// loop over the todos list and find the provided id.
return state.todos.map(item => {
//gets everything that was already in item, and updates "done"
//else returns unmodified item
return item.id === id ? {...item, done: !item.done} : item
})
}); // set state to new object with updated list
}
const toggleDone = (id) => {
console.log(id);
// copy old state
const newState = {...state, todos: [...state.todos]};
// change value
const matchingIndex = newState.todos.findIndex((item) => item.id == id);
if (matchingIndex !== -1) {
newState.todos[matchingIndex] = {
...newState.todos[matchingIndex],
done: !newState.todos[matchingIndex].done
}
}
// set new state
setState(newState);
}
Something similar to D. Smith's answer but a little more concise:
const toggleDone = (id) => {
setState(prevState => {
// Loop over your list
return prevState.map((item) => {
// Check for the item with the specified id and update it
return item.id === id ? {...item, done: !item.done} : item
})
})
}
All the great answers but I would do it like this
setState(prevState => {
...prevState,
todos: [...prevState.todos, newObj]
})
This will safely update the state safely. Also the data integrity will be kept. This will also solve the data consistency at the time of update.
if you want to do any condition do like this
setState(prevState => {
if(condition){
return {
...prevState,
todos: [...prevState.todos, newObj]
}
}else{
return prevState
}
})
I would create just the todos array using useState instead of another state, the key is creating a copy of the todos array, updating that, and setting it as the new array.
Here is a working example: https://codesandbox.io/s/competent-bogdan-kn22e?file=/src/App.js
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
title: "take out trash",
done: false
},
{
id: 2,
title: "wife to dinner",
done: false
},
{
id: 3,
title: "make react app",
done: false
}
]);
const toggleDone = (e, item) => {
const indexToUpdate = todos.findIndex((todo) => todo.id === item.id);
const updatedTodos = [...todos]; // creates a copy of the array
updatedTodos[indexToUpdate].done = !item.done;
setTodos(updatedTodos);
};