I'm writing a small component that decorates some children. Given the following sample:
const Child = props => <div>{props.name}</div>;
const Parent = props => {
let wrappedChildren = React.Children.map(props.children, c => {
return (<Decorator key={c.key}>
{c}
</Decorator>);
});
return (<div>
{wrappedChildren}
</div>);
}
const Consumer = props => {
let children = [0, 1, 2].map(num => {
return <Child key={num} name={num} />;
});
return <Parent>{children}</Parent>;
});
In this code, I'm wanting to take each child and decorate it with some wrapping container or some behaviour. Forgetting for the moment that there may only be one child, I need to give each instance a key.
Currently I'm assuming that each child does have a key which isn't fantastic, lifting it off the child and applying it to the Decorator directly.
Is this the "correct" way of doing this? Is there a better way?
I think your approach is fine. And you do need the key on the top level. Use the child's key, if it is there. If not, fall back to the index, as React recommends:
When you don't have stable IDs for rendered items, you may use the item index as a key as a last resort.
Be advised though:
We don't recommend using indexes for keys if the items can reorder, as that would be slow.
Source: React Docs about keys
const Child = props => <div>{props.name}</div>;
const Parent = props => {
let wrappedChildren = React.Children.map(props.children, (c, i) => {
const key = c.key ? `key-${c.key}` : `index-${i}`
return (
<Decorator key={key}>
{c}
</Decorator>
);
});
return (
<div>
{wrappedChildren}
</div>
);
};
const Consumer = () => {
let children = [ 0, 1, 2 ].map(num => {
return <Child key={num} name={num} />;
});
return <Parent>{children}</Parent>;
};
That would work with the current version of React, 15.6.1 (and probably with prior versions as well). However, there is a slightly better way to achieve your goal with a small tweak, which would be delegating the lifting on a prop, rather than using directly the key.
The reason is that the concept of a key is something that is controlled by React internals before your component gets created, so it falls into the implementation details category, with the consequence that future versions of React could break your code without you even noticing it.
Instead, you can use a prop on your Child component, for instance id, based on your assumption that each child does have some sort of unique value. Your code would result in:
const Child = props => <div>{props.name}</div>;
const Parent = props => {
return React.Children.map(props.children, c => {
return (<Decorator key={c.props.id}>
{c}
</Decorator>);
});
return (<div>
{wrappedChildren}
</div>);
}
const Consumer = props => {
let children = [0, 1, 2].map(num => {
return <Child id={num} name={num} />;
});
return <Parent>{children}</Parent>;
});
If you have a more complex structure and want to avoid passing props individually, the parent component should held the responsibility of generating unique ids for the children. You can follow the ideas explained in this answer https://stackoverflow.com/a/29466744/4642844.
Those are the steps I'd follow:
Implement your own utility function or use an existing browser library to generate unique ids.
Implement componentWillMount in your Parent component to create an array of unique ids. You can use an internal member class variable instead of local state. It would be something like:
componentWillMount() {
this.uuids = React.Children.map(() => uuid());
}
Inside your Parent render, assign each key to the Decorator component in the following way.
render() {
return React.Children.map(props.children, (c, i) => {
return (<Decorator key={this.uuids[i]}>
{c}
</Decorator>);
});
}
Some useful links:
http://mxstbr.blog/2017/02/react-children-deepdive/
https://medium.com/#dan_abramov/react-components-elements-and-instances-90800811f8ca
Javascript Array.map(fn) passes actually 3 arguments to fn second being an index. So, what you need to do is:
let wrappedChildren = props.children.map((c, i) => <Decorator key={i}>{c}</Decorator>);
Related
I would love getting some help on this one, I think I am getting there, but I am not sure about it and need some guidance.
I have a parent component, which renders multiple subcomponents, and on each of those subcomponents, I get a result from a hook that do a lot of calculations and other multiple hook calls.
This hook only accepts and a single entity, not arrays, and I cannot afford to modify everything in order to accept arrays in them.
So let's say my parent component is
const Parent = () => {
const cardsArray = [...]
return (
<Wrapper>
{cardsArray.map(
card => <CardComponent cardId={cardId} />
)}
</Wrapper>
)}
and my subComponent :
const CardComponent = ({cardId}) => {
const result = useCalculation(cardId)
return (
<div>My Calculation Result: {result}</div>
)}
Now my issue is this: I need to sum up all those results and show them in my Parent Component. What would be my best way to achieve this?
I thought about having an update function in my parent and pass it as a prop to my subcomponents, but, I am getting the problem that when the Card Subcomponent gets the result from the hook, calls the function and updates the parent state, although it works, I get an error on the console saying that I am performing a state update while rendering:
Cannot update a component (Parent) while rendering a different component (CardComponent). To locate the bad setState() call inside CardComponent, follow the stack trace as described in https://github.com/facebook/react/issues/18178#issuecomment-595846312
I feel like the answer must not be hard but I am not seeing it
thanks a lot
I made some assumptions about your implementation but i think it will cover your needs.
Your thought about having an updater function on the parent element and pass it to it's children sounds pretty good and that's the basic idea of my proposed solution.
So let's start with the Parent component:
const Parent = () => {
const cardsArray = [
{ cardId: 1 },
{ cardId: 2 },
{ cardId: 3 },
{ cardId: 4 }
];
const [sum, setSum] = useState(0);
const addToSum = useCallback(
(result) => {
setSum((prev) => prev + result);
},
[setSum]
);
return (
<div>
{cardsArray.map(({ cardId }) => (
<CardComponent key={cardId} cardId={cardId} addToSum={addToSum} />
))}
<strong>{sum}</strong>
</div>
);
};
I named your updater function addToSum assuming it aggregates and sums the results of the children elements. This function has 2 key characteristics.
It's memoized with a useCallback hook, otherwise it would end up in an update loop since it would be a new object (function) on every render triggering children to update.
It uses callback syntax for updating, in order to make sure it always uses the latest sum.
Then the code of your child CardComponent (along with a naive implementation of useCalculation) would be:
const useCalculation = (id) => {
return { sum: id ** 10 };
};
const CardComponent = memo(({ cardId, addToSum }) => {
const result = useCalculation(cardId);
useEffect(() => {
addToSum(result.sum);
}, [result, addToSum]);
return <div>My Calculation Result: {JSON.stringify(result)}</div>;
});
The key characteristics here are:
the updater function runs on an effect only when result changes (effect dependency).
the addToSum dependency is there to make sure it will always run the correct updater function
it is a memoized component (using memo), since it has expensive calculations and you only want it to update when it's props change.
I assumed that useCalculation returns an object. If it returned a primitive value then things could be a little simpler but this code should work for every case.
You can find a working example in this codesandbox.
Create a state in the parent (sum in the example), and update it from the children in a useEffect block, which happens after rendering is completed:
const { useEffect, useState } = React
const useCalculation = cardId => cardId * 3
const CardComponent = ({ cardId, update }) => {
const result = useCalculation(cardId)
useEffect(() => {
update(result)
}, [result])
return (
<div>My Calculation Result: {result}</div>
)
}
const Parent = ({ cardsArray }) => {
const [sum, setSum] = useState(0);
const updateSum = n => setSum(s => s + n)
return (
<div>
{cardsArray.map(
cardId => <CardComponent key={cardId} cardId={cardId} update={updateSum} />
)}
sum: {sum}
</div>
)
}
ReactDOM.render(
<Parent cardsArray={[1, 2, 3]} />,
root
)
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
I have the following code:
export default function Parent() {
const children1 = someArrayWithSeveralElements.map(foo => <SomeView />);
const children2 = someArrayWithSeveralElements.map(foo => <SomeCheckbox />);
return (<>
{children1}
{/*Some other components*/}
{children2}
</>)
};
For a given element foo, there is a SomeView component that is conditionally rendered based on the state of a SomeCheckbox. I'm having trouble figuring out a way to have the state from the checkbox affect the rendering of the sibling view component.
Normally the solution would be to just declare the state hook in the parent component and pass them down to each child, but since the siblings are rendered via foreach loops it's impossible to do so.
My current solution is to also generate the state hooks for each foo in a loop as well, but that feels a bit hacky since it's better to avoid creating hooks inside of loops (it's worth nothing that someArrayWithSeveralElements is not intended to change after mounting).
Is there a more elegant alternative to solve this?
The solution is what you side, you need to create a state in the parent component and pass it to the children. and this will work for single component or bunch of them, the difference is just simple: use array or object as state.
const [checkboxesStatus, setCheckboxesStatus] = useState({// fill initial data});
const children1 = someArrayWithSeveralElements.map(foo =>
<SomeView
visibile={checkBoxesStatus[foo.id]}
/>);
const children2 = someArrayWithSeveralElements.map(foo =>
<SomeCheckbox
checked={checkBoxesStatus[foo.id]}
onChange={// set new value to foo.id key}
/>)
export default function Parent() {
const [states, setStates] = React.useState([]);
const children1 = someArrayWithSeveralElements.map((foo, i) => <SomeView state={states[i]} />);
const children2 = someArrayWithSeveralElements.map((foo, i) => {
const onStateChange = (state) => {
setStates(oldStates => {
const newStates = [...(oldStates || [])]
newStates[i] = state;
return newStates;
})
}
return <SomeCheckbox state={states[i]} onStateChange={onStateChange} />;
});
return (<>
{children1}
{/*Some other components*/}
{children2}
</>)
};
Use states in the parent componet.
Note: the element of states may be undefined.
After a week of learning React and also diving into React Hooks, I stumbled into the problem of communicating between components like the following:
parent to child
child to parent
child to child (siblings)
I was able to communicate from a child to its own parent by adding a prop with the name onChange and passing by a function that is defined on its parent.
So this is what I had in the parent:
function handleChange(val: any) {
console.log(val)
console.log(hiddenPiece)
}
return (
<div className="board-inner">
{puzzlePieces}
</div>
)
And this is the child:
props.onChange(props.index);
The question really is, how am I able to communicate from the parent straight with its child after like a click state or when the children's state change? I have been searching for easy samples, but I guess I am not good at my research for now. I need someone who can help me out with clear examples. Thanks for taking the time to help me out here.
Here's a very basic example for the two of cases you described (parent > child & child > parent). The parent holds state, has some functions to modify it and renders two childs.
https://codesandbox.io/s/silly-browser-y9hdt?file=/src/App.tsx
const Parent = () => {
const [counter, setCounter] = useState<number>(1);
const handleIncrement = () => {
setCounter((prevCount) => prevCount + 1);
};
const handleDecrement = () => {
setCounter((prevCount) => prevCount - 1);
};
// used as prop with the children
const doubleTheCounter = () => {
setCounter((prevCount) => prevCount * 2);
};
return (
<div>
<h1>Parent counter</h1>
<p>{counter}</p>
<button onClick={handleIncrement}>+</button>
<button onClick={handleDecrement}>-</button>
<ChildTriple countFromParent={counter} />
<DoubleForParent doubleCallback={doubleTheCounter} />
</div>
);
};
The first child receives state from the parent and uses that do display something different ("triple" in this case):
type ChildTripleProps = { countFromParent: number };
// Receives count state as prop
const ChildTriple = ({ countFromParent }: ChildTripleProps) => {
const [tripleCount, setTripleCount] = useState<number>(countFromParent * 3);
useEffect(() => {
setTripleCount(countFromParent * 3);
}, [countFromParent]);
return (
<div>
<h1>Child triple counter</h1>
<p>{tripleCount}</p>
</div>
);
};
The second child receives a callback function from the parent, which changes the state at the parent:
type DoubleForParentProps = { doubleCallback: () => void };
// Receives a function as prop, used to change state of the parent
const DoubleForParent = ({ doubleCallback }: DoubleForParentProps) => {
const handleButtonClick = () => {
doubleCallback();
};
return (
<div>
<h1>Child double counter</h1>
<button onClick={handleButtonClick}>Double the parent count</button>
</div>
);
};
For your third case (child <> child) there are a lot of different options. The first one is obviously holding state in their parent and passing that down to both childs, similar to the parent in this example.
If you have grandchildren or components even further apart in the tree it probably makes sense to use some kind of state management solution. Most of the time the built in React context is totally sufficient. If you want to go for best practices regarding context I highly recommend Kent C. Dodds' blog post. This will also help you get to know the React eco system better.
External state libraries are, in my opionion, either a) too complex as a beginner, b) really new and not battle proven or c) not a best practice anymore or overblown.
I created a react functional component to wrap sections of code based on a predicate (instead of using ternary within JSX).
So, the component looks like this:
const PredicateWrapper = ({ children, iff, otherwise }) => (
iff ? children : (otherwise || null)
);
And can be used like this:
<PredicateWrapper iff={students}>
{students.map(student => <Student />)}
</PredicateWrapper>
The issue is that this throws an error "cannot read property map of null" if students is null. If I replace the students.map with some text, the text does not render, proving the wrapper is working, however, the point of the wrapper is to deal with cases where students is null, and I would expect it not to "enter" inside the wrapper.
How can I achieve something like this that allows for the inner code to not evaluate and throw runtime errors if iff is falsy?
Really interesting question!
I could be woefully wrong here but I think it's because students.map(student => <Student /> is still part of your parent component's render tree. So when React tries to build the parent component's tree, it's trying to execute that line which throws an error.
PredicateWrapper is mounted and resolved when the child components are recursed upon, which is when the iff would kick in - but that's on the second iteration.
For eg, if I do this, I don't get the error
const Child = ({ students }) => {
return students.map(student => <Student />);
};
<PredicateWrapper iff={students}>
<Child students={students} />
</PredicateWrapper>
Let's have a look at how the JSX will be compiled to JavaScript with the online Babel compiler:
const PredicateWrapper = ({ children, iff, otherwise }) =>
iff ? children : otherwise || null;
const students = null;
const App = () =>
React.createElement(
PredicateWrapper,
{
iff: students,
},
students.map((student) => React.createElement(Student, null))
);
Now you can see students.map will be executed and cause the error "Cannot read property map of null"
One possible implementation to achieve your goal is to use render props:
The term “render prop” refers to a technique for sharing code between
React components using a prop whose value is a function.
const PredicateWrapper = ({ render, iff, otherwise = () => null }) =>
iff ? render() : otherwise();
const StudentA = () => <span>😎</span>;
const StudentB = () => <span>😂</span>;
const students = [StudentA, StudentB];
//const students = null;
function App() {
return (
<PredicateWrapper
iff={students}
render={() => students.map(Student => <Student />)}
/>
);
}
The parent component contains an array of objects.
It maps over the array and returns a child component for every object, populating it with the info of that object.
Inside each child component there is an input field that I'm hoping will allow the user to update the object, but I can't figure out how to go about doing that.
Between the hooks, props, and object immutability, I'm lost conceptually.
Here's a simplified version of the parent component:
const Parent = () => {
const [categories, setCategories] = useState([]);
useEffect(()=>{
// makes an axios call and triggers setCategories() with the response
}
return(
categories.map((element, index) => {
return(
<Child
key = {index}
id = {element.id}
firstName = {element.firstName}
lastName = {element.lastName}
setCategories = {setCategories}
})
)
}
And here's a simplified version of the child component:
const Child = (props) => {
return(
<h1>{props.firstName}</h1>
<input
defaultValue = {props.lastName}
onChange={()=>{
// This is what I need help with.
// I'm a new developer and I don't even know where to start.
// I need this to update the object's lastName property in the parent's array.
}}
)
}
Maybe without knowing it, you have lifted the state: basically, instead of having the state in the Child component, you keep it in the Parent.
This is an used pattern, and there's nothing wrong: you just miss a handle function that allows the children to update the state of the Parent: in order to do that, you need to implement a handleChange on Parent component, and then pass it as props to every Child.
Take a look at this code example:
const Parent = () => {
const [categories, setCategories] = useState([]);
useEffect(() => {
// Making your AXIOS request.
}, []);
const handleChange = (index, property, value) => {
const newCategories = [...categories];
newCategories[index][property] = value;
setCategories(newCategories);
}
return categories.map((c, i) => {
return (
<Child
key={i}
categoryIndex={i}
firstName={c.firstName}
lastName={c.lastName}
handleChange={handleChange} />
);
});
}
const Child = (props) => {
...
const onInputChange = (e) => {
props.handleChange(props.categoryIndex, e.target.name, e.target.value);
}
return (
...
<input name={'firstName'} value={props.firstName} onChange={onInputChange} />
<input name={'lastName'} value={props.lastName} onChange={onInputChange} />
);
}
Few things you may not know:
By using the attribute name for the input, you can use just one handler function for all the input elements. Inside the function, in this case onInputChange, you can retrieve that information using e.target.name;
Notice that I've added an empty array dependecies in your useEffect: without it, the useEffect would have run at EVERY render. I don't think that is what you would like to have.
Instead, I guest you wanted to perform the request only when the component was mount, and that is achievable with n empty array dependecies;