Spreading props in react while maintaining reference equality - javascript

Imagine that I have a function which dynamically generates some of a component's props, and I want to pass them all at once without being explicit about every prop the function could generate. Normally you can do this with the spread operator, but the issue with the spread operator is that it created a new object each time. This would mean that (if I understand correctly) during the React Reconciliation, the component would have new props every time and would rerender every time, even if the props the function generated are the same.
Here's a concrete example:
const generateProps = () => ({foo: 'bar'});
const ParentComponent = () => ({
const someProps = generateProps();
return (
<SomeComponent><ChildComponent {...someProps} otherProp='hello world'/></SomeComponent>
)
})
Here ChildComponent would render every time ParentComponent would render (right?). One thing I know you could do is wrap the ChildComponent with a React.memo and do a deeper comparison of the props (passing a custom comparison function to it), but what if you don't have control over ChildComponent? Are you forced into being explicit? Or am I incorrect and ChildComponent wouldn't rerender in this example (assuming ChildComponent simply consumes the props and doesn't use any contexts or anything).
Thank you!

You have it wrong. Reconciliation doesn't look at the props. It mainly looks at the component type, e.g.
if on one render you render
<Comp1/>
and on next render, on the same place in the component tree, you render:
<Comp2/>
it will unmount Comp1 and mount Comp2 because the type of components is different. If component types are the same, it will update existing one. There are some more details but you can check them yourself.
Furthermore, the props are also compared in a shallow way by default if you use React.memo, so if on one render you pass
let y = {a:1,b:2};
....
<Comp1 {...y}/>
and on next render you pass
let x = {a:1,b:2};
...
<Comp1 {...x}/>
Default comparison of React.memo will assume that props didn't change, because a and b have same values.
You can verify here, clicking on the div doesn't re render the Test component:
let Test = React.memo(props => {
console.log(props);
return <div>{props.a}</div>;
});
function App() {
let [state, setState] = React.useState({ a: 123 });
return (
<div
onClick={() => {
setState({ a: 123 });
}}
>
<h1>Hello StackBlitz!</h1>
<Test {...state} />
<p>Start editing to see some magic happen :)</p>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Related

How can you re-render a React functional component with new props?

I want to do something like this:
const GreetingWithCounter = (props) => {
const { name, count } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={() => render({ ...props, count: count + 1 })}>
{count}
</button>
</div>
);
}
<GreetingWithCounter name="Alice" count={0} />
Ie. I want to re-render a component with new values for its props. Is there a way to do that? Looking through these three questions, I'm seeing ways to re-render a component but not with new values for props (1, 2, 3).
Context
I'm thinking about a way to simplify React. I really like the mental model of React being the view layer in MVC, where UI = F(state). But things can get confusing when "state" can come from so many different places: props, useState, useReducer, "raw" useContext, Redux (which uses useContext I think), whatever else.
What if everything was just based off of props?
For local state you'd do what I did in that example above. You'd initialize the local state of count when doing <GreetingWithCounter name="Alice" count={0} /> and then update it by re-rendering. This means less DRYness because you'd have to repeat the count={0} code instead of only having it once inside of GreetingWithCounter.
You'd have to do prop drilling instead of useContext stuff.
This approach would probably make React slower.
Still, I hypothesize 1) that the mental model of having everything coming from props is simpler and 2) that pro outweighs the cons in a non-trivial amount of apps.
Props are not supposed to be mutated in React. That is precisely the difference between props and state. The React way to do this is to use state for the count. You can pass the initial state of the count as a prop and do this: const [count, setCount] = useState(initialCount). Your onClick handler would then increment count, which again is state. I realize that this is not what you want but it's how React works.
In React Props values cannot be changed in child component but we can do it in parent component.
const GreetingWithCounter = (props) => {
const { name, count, updateCount } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={updateCount}>{count}</button>
</div>
);
};
function App() {
const [count, setCount] = useState(0);
const updateCount = () => {
setCount(count + 1);
};
return (
<div className='App'>
<h1>Greeting With Counter:</h1>
<GreetingWithCounter
name='Alice'
count={count}
updateCount={updateCount}
/>
</div>
);
}
Appreciate the change you want to point out and value you want to add but there might be some points that you're missing what React conceptually trying to provide with seperation between props and state.
The props within components coming with React are specifically conceptually designed to be immutable as per the documentation
here.
So what you're trying to do is conceptually not ok for that purpose and violating what React tries to accomplish.
Infact you may mention about creating another library/framework which successfully getting it done while introducing props are the new state concept but in this specific case, there's no possible way to succeed on it in a React way.
You cannot change value of props in child but you have 2 ways to handle it
first, I assume that you only want to use count in child component and you don't need count value in parent, in this case you can use props.count as initial state, sth like this :
const GreetingWithCounter = props => {
const [count, setCount] = useState(props.count);
const { name } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={() => setCount(prevState => prevState + 1)}>{count}</button>
</div>
);
};
<GreetingWithCounter name="Alice" count={0} />;
but if you wanna access it's value from parent, it's better to pass setter to child
sth like this :
const GreetingWithCounter = ({name,count,setCount}) => {
return (
<div>
<div>Hello {name}</div>
<button onClick={() => setCount(prevState => prevState + 1)}>{count}</button>
</div>
);
};
const App = ()=>{
const [count, setCount] = useState(0);
return (<GreetingWithCounter name="Alice" count={count} setCount={setCount} />)
}
or if it's child is so deep that you need to send props to all it's tree, its better to use state management like Redux,Context or ...
Is this the way you want to do ? :
import React from 'react'
import ReactDOM from 'react-dom'
export default function renderComponent(Component, props, container) {
ReactDOM.render(<Component {...props} />, container)
}
What you are trying to do goes against the philosophy of state management of react. For correct way to do it, you can check other answers, and even you yourself have posted it in the questions.
But if you really want to do it, behind its magic, React is also just JavaScript. Therefore, we just need to implement the render function outside of React way of thinking. We know that React re-renders on state change magic or on props change. We need to just somehow connect the render method you asked for with set state. Something like the below should work.
const ParentStuff = () => {
const [props, setProps] = useState({ name: "Alice", count: 0 });
render = setProps;
return (<GreetingWithCounter name={props.name} count={props.count} />);
}
let render;
const GreetingWithCounter = props => {
const { name, count } = props;
return (
<div>
<div>Hello {name}</div>
<button onClick={() => render({ ...props, count: count + 1 })}>{count}</button>
</div>
);
};
A lot of people will scream though at code above. It definitely strays away from the intended use.
If you want to go further, you can also just have one state for the entire app, and pass this state fo every component. And voila! You just created a singleton state and an uni directional data flow, which is a poor man version of the redux and this will probably kill performance of the webapp, as things like typing each letter in a textbox will re-render the entire page.
As others already mentioned, component is either controlled or uncontrolled (or mix of both) in react.
If you keep state in component itself - it's uncontrolled. You can reset its state to internal by changing key prop from parent though.
If you keep state in parent - it's controlled component and changes it's state through props/callbacks.
What you have shown in your example, you want to achieve uncontrolled component with some syntactic sugar on top.
Example implementation:
const usePropsWithRender = (props) => {
const [currentProps, setCurrentProps] = useState(props);
return {
...currentProps,
render: setCurrentProps,
};
};
const GreetingWithCounter = (props) => {
const { name, count, render } = usePropsWithRender(props);
return (
<div>
<div>Hello {name}</div>
<button onClick={() => render({ ...props, count: count + 1 })}>
{count}
</button>
</div>
);
};
You can reuse usePropsWithRender through all you project, but it's nothing more than a thin wrapper around useState. I don't see how it is better than using useState directly.

How React HOC function knows the props of a Component that put into it as a parameter

I try to understand how React HOCs work. The following code that will be written bellow WORKS, but I cannot understand WHY it works:
const MyHOC = (WrappedComponent) => {
return (props) => { //these props are the props of a WrappedComponent
return (
<div style={{color: "red", fontSize: "100px"}}>
<WrappedComponent {...props}/>
</div>
)
}
}
export default MyHOC
After that, I use my hoc here:
import MyHOC from "../HOC/MyHOC" //hoc
const SomeStuff = (props) => {
return (
<div>
Hello world!!!
{
props.data.map(item => {
return <div>{item}</div>
})
}
</div>
)
}
//our component wrapped with a hoc
export default MyHOC(SomeStuff)
And then, I implement my components here, where they successfully work
import SomeStuff from "./COMPONENTS/SomeStuff"
function App() {
const list = ['apple', 'orange', 'lemon']
return (
<div>
<SomeStuff data={list}/>
</div>
);
}
export default App;
In the first snippet of a code written above, we created a function that takes "WrappedComponent" as a parameter. This function returns another function, which takes props as a parameter.
MY QUESTION IS:
HOW THE RETURNED FUNCTION KNOWS WHAT PROPS PUT AS PARAMETER, IF WE DID NOT DECLARE THEM ANYWHERE.
We put a component as a parameter to a PARENT function, BUT React SOMEHOW FOUND OUT THAT WE NEED THE PROPS used in this "WrappedComponent"
How this was possible?
Please, please, please help me to find an answer...
Thank you a lot in advance
You did declare them using <WrappedComponent {...props}/>, that will take the props that you passed to the HOC and also pass it down to the child. The spread operator ... will split up the prop array the HOC received and pass it on to the child as individual props, as if you just normally called that component.
SomeStuff is being with data as a prop in main App.
SomeStuff being exported is wrapped by HOC as declared.
export default MyHOC(SomeStuff)
So, component ie. function that is exported from SomeStuff module is somewhat:
(props) => { //these props are the props of a WrappedComponent
return (
<div style={{color: "red", fontSize: "100px"}}>
<WrappedComponent {...props}/>
</div>
)
}
Here, WrappedComponent is SomeStuff and the data (ie. prop) you passed is being passed as it is to the SomeStuff component.
HOW THE RETURNED FUNCTION KNOWS WHAT PROPS PUT AS PARAMETER, IF WE DID
NOT DECLARE THEM ANYWHERE.
When you peform:
<SomeStuff data={list}/>
you're passing the data prop into the SomeStuff (wrapped) component, which is exported here:
export default MyHOC(SomeStuff)
this first calls the MyHOC() function and returns the (props) => { function, which acts as the wrapping component for SomeStuff. This wrapping component is used when we export it (seen above at the beginning of this answer). As a result, the wrapping component gets passed through data={list} which is accessed via the props argument of the returned (props) => { function - in your example, props would be an object that looks (roughly) like so {data: list}.
React SOMEHOW FOUND OUT THAT WE NEED THE PROPS used in this
"WrappedComponent"
Your wrapping component is able to forward the props object into the actual SomeStuff component via the spread syntax:
<WrappedComponent {...props}/>
this takes every (own-enumerable) key: value pair from the props object, and adds it as a key={value} prop to the target component (that being the actual SomeStuff component).

How to make React.memo rerender logic deterministic in React functional components

I have a functional React component that uses useState and useContext, and it derives its display properties from both context and props.
I am using useMemo with its second argument, the areEqual function to prevent the component from re-rendering when new props come in. There are dozens of values for the prop, but I only want to re-render the component when the prop changes to a specific value.
Here is a simplified example:
const MyComponent = (props) => {
const {contextThing} = useContext();
const {stateThing} = useState();
const {propsThing} = props;
// Since only foo changes what is rendered, I want to prevent rerendering unless the
// prop changes to 'foo'
const derivedThing =
(propsThing === 'foo' && contextThing === 'bar') ?
'foobar' :
null;
return (
<>
<div>{contextThing}</div>
<div>{stateThing}</div>
<div>{propsThing}</div>
<div>{derivedThing}</div>
<>
}
function preventRerender(prevProps, nextProps) {
if(prevProps.propsThing !== 'foo' }} nextProps.propsThing !== 'foo') {
return true;
}
return false;
}
export default memo(MyComponent, preventRerender);
This successfully prevents the component from re-rendering, because one level up the tree, the MyComponent is wrapped with MyComponentContainer, which uses a custom hook
to drill the props into MyComponent, like so:
const MyComponentContainer = () {
// Note: `useMyCustomHook` uses `useContext`, `useReducer` and `useState`
const { propsThing } = useMyCustomHook();
return (
<>
<Header />
<MyComponent propsThing={propsThing} />
<Footer />
<>
);
}
export default MyComponentContainer;
The Problem
In reference to the areEqual function:
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.
https://reactjs.org/docs/react-api.html#reactmemo
React docs seem to say this is a bad practice. I'm essentially treating memo/areEqual like a shouldComponentUpdate in a class component.
I don't have control over when propsThing changes. It is driven by both user actions and by pub/sub to a 3rd party API.
Is this an anti-pattern that I need to replace with a different pattern?
Or is React just saying "don't rely on this for features, because we may change how it works?".
Is there are 3rd party solution to better handle this use case?

Pass a functional component as argument, then use it in JSX

I have a pair of functional components and want to pass one in as an argument to the other as shown below:
const compA = () => ( <div> Hello World! </div>);
const compB = (AnotherComp) => ( <AnotherComp />);
compB(compA);
The above snippet is a dumbed-down version of what I'm after, as each component has its own set of React hooks.
Google-fu turned up many guides on Higher-Order Components, but most address the difference between HOC and react hooks, or for getting props from a class component into a functional sub-component.
Is there a way to use Higher-order functional components, as shown above?
I hope I'm not misunderstanding your question, but it seems to me that for something to be considered higher order, you need a function to return a function, as in makeBlue, below.
const Button = ({ children, ...props }) => (
<button {...props}>{children}</button>
);
const makeBlue = (Component) => (props) =>
<Component style={{ background: "blue" }} {...props} />;
const BlueButton = makeBlue(Button);
Yes, they work with functional components.

React: How to do conditional checks from render() method in functional Components?

In my normal React class Components, I have done some checks in the render() method, before returning conditional html rendering. Now, I was using a react functional component, which apparently does not have the render() method... how would I do the conditional checks here? Just Inside normal functions and then return html code from those functions?
e.g Class Component:
render() {
let test;
if (this.state.test ===true) {
test = (
<p>This is a test</p>
)
}
return(
{test}
)
}
in functional components? :
return (
<p >
{checkIcon()} //normal Javascript functions?
</p>
)
As stated by others you can do anything inside a render function, the same things you could do with a class component. You can think of your functional components as the render function of your class ones...
Functional components, by the way, should not contain that much business logic, it'd be better to enhance them with HOCs and function composition.
You might want to have a look at recompose, in which my example takes inspiration from. (change the test attribute and press run code snippet)
// First create a Generic HOC that embedds the branching logic for you.
const branch = (predicate, LeftComponent) => RightComponent => props => (
predicate(props) ? <LeftComponent {...props} /> : <RightComponent {...props} />
);
// Leave your view component the only job of displaying data to the screen. Avoid any control flow.
const Test = () => 'this is a test component';
const Value = ({ value }) => <div>The Value is {value}</div>;
// Create a final component that branches accordingly with the needed check (if props.test is true)
const Component = branch(
props => props.test,
Test
)(Value);
ReactDOM.render(
<Component test={true} value="£100" />,
document.getElementById('container')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="container"></div>
You can think of functional component as a render method of class component where you can do the exact same thing that you do in render except that you will receive props from the arguments instead of this and similarly you won't have state unless your using hooks. So you would pass test as a prop to the functional component
const MyComponent = ({test}) =>{
let value;
if (test ===true) {
test = (
<p>This is a test</p>
)
}
return(
{value}
)
}

Categories