So I've done some reading on here and saw that most people recommended against modifying props. Thus, I was wondering if there's a way I can duplicate the prop?
Essentially, I want to duplicate the prop and set it to the state. More specifically, I'm creating a table and one of the props that is passed in is the headers which is an array of objects
headers={[
{id: "Name" , numeric: false, disablePadding: false, label: "Name"},
{ id: 'Weight', numeric: true, disablePadding: false, label: 'Weight' },
{ id: 'Height', numeric: true, disablePadding: false, label: 'Height' }
}]
I want to add a default column s.t. it'll look like
headers={[
{id: "Name" , numeric: false, disablePadding: false, label: "Name"},
{ id: 'Weight', numeric: true, disablePadding: false, label: 'Weight' },
{ id: 'Height', numeric: true, disablePadding: false, label: 'Height' },
{id: "Modify" , numeric: false, disablePadding: false, label: "Modify"}
]}
Thanks for your help! :-)
There are a few techniques you could use without using additional libraries
1. Set the initial state properly
this.state = { headers: this.props.headers.slice(0) }
When modifying the state use the callback technique
this.setState((previousState) => {
// mutate the state a return a new one.
});
How to use slice
Using setState properly
Array object is passed by reference. Instead you can create a new array and then dump the data into the state.
this.state = {
headers: (() => {
return [...this.props.headers, {id: "Modify" , numeric: false, disablePadding: false, label: "Modify"}]
})()
}
Create the component which you have to add.
let newObject = { "id": "Modify" , "numeric": false, "disablePadding": false, "label": "Modify" }
Now, create a duplicate along with the newObject which was created.
const headers = [...this.props.headers, newObject]
Now, set that headers to state variable header.
this.setState({
header: headers
})
I hope this will work for you.
Do this in constructor:
this.state: {fooCopy: this.props.foo};
if you want to store modified prop in state, then make a copy in a local var (try Object.assign or JSON.parse(JSON.stringify(a))), modify it, and then store in state.
Related
I’ve got a Next.JS app. I want to render a todo feed on the homepage, but also the user page. I'm a bit stuck on how to break down my Prisma queries.
I fetch a big data object using getServerSideProps and pass this to the page component (and using react-query to hydrate and do re-fetching, but not relevant now)
- getRecentTodos (includes todos) for my homepage
- getUserDetailsByName (includes todos) for the user page
export type getRecentTodos = ReturnType<typeof getRecentTodos> extends Promise<
infer T
>
? T
: never
export const getRecentTodos = async (recentItemsAmount = 20) => {
return await prisma.todos.findMany({
where: { done: true },
select: {
id: true,
userId: true,
content: true,
done: true,
createdAt: true,
attachments: true,
todoReplies: {
select: {
id: true,
userId: true,
content: true,
todoReplyLikes: true,
todoId: true,
user: { select: { name: true, displayName: true, image: true } },
},
orderBy: { createdAt: 'asc' },
},
todoLikes: {
select: {
user: true,
},
},
user: {
select: {
name: true,
displayName: true,
image: true,
},
},
},
orderBy: { createdAt: 'desc' },
take: recentItemsAmount,
})
}
export const getUserDetailsByName = async (username: string) => {
return await prisma.user.findUnique({
where: {
name: username,
},
select: {
name: true,
displayName: true,
bio: true,
location: true,
twitter: true,
image: true,
createdAt: true,
todos: {
select: {
id: true,
content: true,
userId: true,
done: true,
updatedAt: true,
createdAt: true,
attachments: true,
user: true,
todoLikes: true,
todoReplies: {
take: 30,
orderBy: { createdAt: 'desc' },
select: {
id: true,
userId: true,
todoId: true,
createdAt: true,
content: true,
user: true,
},
},
},
take: 30,
orderBy: { createdAt: 'desc' },
},
projects: true,
},
})
}
Both queries return ‘todos,’ but they can return it in a slightly different way. The todo feed component expects certain properties to be available
- E.g. displayName on todoReplies
- But on getUserDetailsByName the displayName might not be part of the response or it’s nested one layer deeper or something
How to keep this from getting complex very fast?
You more or less want to select todos in your queries the same way (returning the same and omitting the same, apart of some things like order)
But manually keeping these things in sync over lot’s of queries qet’s complex quickly
Possible solutions?
Should I break the getServerSideProps into multiple fetches?
So instead of one ‘getUserDetailsByName’ which has todos as a relationfield included
fetch user details
fetch todos
This would mean I also have to write more react-query code for refetching etc… because you are dealing with multiple objects. But it does seperate concerns more.
Using Typescript to catch it in my codebase when a function tries to access a property which is not returned from that specific Prisma query? (I’m just now starting to see the possibilities of Typescript for stuff like this)
Should I just standardize the way the todos get created in a prisma query with a function and include that function inside of the Prisma queries? you can include like:
const todoSelect = {
id: true,
userId: true,
content: true,
{.......}
user: {
select: {
name: true,
displayName: true,
image: true,
},
},
}
export type getRecentTodos = ReturnType<typeof getRecentTodos> extends Promise<
infer T
>
? T
: never
export const getRecentTodos = async (recentItemsAmount = 20) => {
return await prisma.todos.findMany({
where: { done: true },
select: todoSelect,
orderBy: { createdAt: 'desc' },
take: recentItemsAmount,
})
}
const userSelect = {
name: true,
{......}
todos: {
select: todoSelect,
take: 30,
orderBy: { createdAt: 'desc' },
},
projects: true,
}
export const getUserDetailsByName = async (username: string) => {
return await prisma.user.findUnique({
where: {
name: username,
},
select: userSelect,
})
}
My state is
const [ formInputsProperty, setFormInputsProperty ] = useState(
[
{
id: 0,
name: "courseTitle",
type: "text",
placeholder: "Course Title",
errorMessage: "Course Title should be 3-16 characters and shouldn't include any special character!",
label: "Course Title",
pattern: "^[A-Za-z0-9]{3,16}$",
required: true,
isFocused: false
},
{
id: 1,
name: "shortDesc",
type: "text",
placeholder: "Course Short Description",
errorMessage: "Course Description should be 10-50 characters and shouldn't include any special character!",
label: "Course Short Description",
pattern: "^[A-Za-z0-9]{10,50}$",
required: true,
isFocused: false
}
]
)
Now i want to update isFocused to true of all elements of the given state. How can I do that? I tried map but was not successfull.
You can do it by using the map function and then setting the state afterwards.
Example:
const updateFocus = () => {
setFormInputsProperty((prev) => (
prev.map((item) => ({...item, isFocused: true}))
);
};
Explanation:
You access the current state with prev and then use the map function on it. With map you iterate over every element in the list and can do what ever you want with it. In this case we just set the isFocused property to true. The return value inside the map function always replaces the current item.
More information on MDN about map
More information on MDN about spread syntax
I have a reducer which updated a field's value inside an array like so:
return {
...state,
[action.field]: { ...state[action.field], value: action.payload }
};
This works fine but my state is an array of named objects (I think these are called object literals) like so:
export const state: any = {
"account_name": {
label: 'Account Name',
type: 'text',
name: 'account_name',
value: '',
required: true
},
"account_website": {
label: 'Website',
type: 'url',
name: 'account_website',
value: '',
required: false
},
My reducer works fine and updates the object as required, however it reorders the entire array in alphabetical order.
However if I change my update function to the following it works fine:
state[action.field] = action.payload
return state
I ended up changing my object (string->value mapping) to a regular array like so:
export const newAccountFields: any = [
{
label: 'Account Name',
type: 'text',
name: 'account_name',
value: '',
required: true,
},
{
label: 'Contact',
type: 'contacts',
name: 'account_contact',
value: '',
required: true,
},
Then when using the reducer my order is unchanged
I have some nested state in class component that I want change it to functional component, but I don't know how can I use nested state in functional component with hook and useState and also useEffect? can you help me please?
state = {
products: null,
totalPrice: 0,
addCounter: 0,
purchased: false,
loading: false,
form: {
name: {
elementType: 'input',
elmentConfig: {
type: 'text',
palceholder: 'Name...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
password: {
elementType: 'input',
elmentConfig: {
type: 'password',
palceholder: 'Password...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
email: {
elementType: 'input',
elmentConfig: {
type: 'text',
palceholder: 'Email...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
},
};
Convert your state with useState, however I would recommend separating your state in smaller 'states' to make it a little more manageable and not have deeply nested values.
eg.
const [checkout, setCheckout] = useState({
products: null,
totalPrice: 0,
addCounter: 0,
purchased: false,
loading: false
});
const [userForm, setUserForm] = useState({
name: {
elementType: 'input',
elementConfig: {
type: 'text',
placeholder: 'Name...'
},
value: '',
validation: {
required: true
},
valid: false,
used: false,
},
password: {
elementType: 'input',
elementConfig: {
type: 'password',
placeholder: 'Password...'
},
value: '',
validation: {
required: true
},
valid: false,
used: false,
},
email: {
elementType: 'input',
elementConfig: {
type: 'text',
palceholder: 'Email...'
},
value: '',
validation: {
required: true
},
valid: false,
used: false,
}
});
nb. you can get your state from the useState hook simply by calling the state name once it has been initialised.
setting your state can become complex when working with deeply nested states so the 'shallower' the better for each state imho.
see documentation here: https://reactjs.org/docs/hooks-state.html with equivalent class examples
You can try:
const [formElements, setFormElements] = useState({
products: null,
totalPrice: 0,
addCounter: 0,
purchased: false,
loading: false,
form: {
name: {
elementType: 'input',
elmentConfig: {
type: 'text',
palceholder: 'Name...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
password: {
elementType: 'input',
elmentConfig: {
type: 'password',
palceholder: 'Password...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
email: {
elementType: 'input',
elmentConfig: {
type: 'text',
palceholder: 'Email...',
},
value: '',
validation: {
required: true,
},
valid: false,
used: false,
},
},
});
By using this, the initial state can be available in formElements and to update it you can use setFormElements function.
Reference
I wonder how I can pass to prop conditionally to my react-table accessor.
eg: I have a data like this:
const data = [
{
name: 'Something',
isA: false,
isB: false,
isC: true
},
{
name: 'Something 2',
isA: true,
isB: false,
isC: false
},
{
name: 'Something 3',
isA: false,
isB: false,
isC: true
},
];
and my two columns:
const columns = [
{ Header: 'Name', accessor: 'name' },
{ Header: 'Is What?', accessor:'?' },
];
Here is in second column accessor how will work like that?
accessor will be that which one of isA, or isB, or isC true, so if isA true accessor will be isA, if isB accessor will be isB ....
How to know it?
In your Is What ? accessor, in order to know which data to show, you can have the accessor like this :
{
Header: "Is What?",
accessor: a => (a.isA ? "isA" : a.isB ? "isB" : a.isC ? "isC" : "")
}
Here, if isA is true, you'll see isA in your Cell value. However, if isA is false and isB is true, you'll see isB in your Cell value etc.