3

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.
Adam Zerner
  • 17,797
  • 15
  • 90
  • 156
  • you are passing hard coded props , so how they will change? – abolfazl shamsollahi Nov 12 '22 at 08:27
  • So you mean, render should take updated prop values and cause a rerender, since the state, props and functions are different for each render cycle, it looks like it could need a wrapper with React.memo that will update the child – Azzy Nov 12 '22 at 09:41
  • @abolfazlshamsollahi I'm not sure, but what I want to do is get it to re-render with _different_ props. Ie. `render({ ...props, count: count + 1 })`. – Adam Zerner Nov 12 '22 at 12:58
  • @Azzy "So you mean, render should take updated prop values and cause a rerender" Yeah. – Adam Zerner Nov 12 '22 at 13:23
  • I don't get it, you want an answer that doesn't involve updating local state? Then maybe don't use React. – morganney Nov 12 '22 at 14:39
  • @morganney Yes, I'd like to re-render the component with new props and avoid local state in the component. – Adam Zerner Nov 12 '22 at 15:29
  • No matter how much you lift state up to pass only props down, you will still need to update state (or context) to cause a rerender in react. Unless of course you do a hack to force an update, but then your approach is flawed and/or you shouldn't be using react. – morganney Nov 12 '22 at 15:41
  • @morganney I disagree that a hack would imply the approach is flawed and/or I shouldn't be using React for the reasons I mentioned in the context section plus the fact that experimenting with ideas is useful. – Adam Zerner Nov 13 '22 at 09:21
  • 5
    If you want to rerender when props change then pass the updated props to your component, that's how react works. Anything else is a hack. – morganney Nov 13 '22 at 13:17
  • React components *always* rerender when they get new props - that's part of the "contract" of a React component. What you seem to be wanting to do is something different - rerender a component from outside without actually updating the value that's passed as a prop. It's not clear why you would want to do that, but it's not how React works and it isn't possible. – Robin Zigmond Nov 15 '22 at 17:36

7 Answers7

2

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.

Neil Girardi
  • 4,533
  • 1
  • 28
  • 45
2

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>
  );
}
iAi Dev
  • 186
  • 5
1

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.

Erhan Yaşar
  • 847
  • 1
  • 9
  • 25
1

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 ...

Ali Sattarzadeh
  • 3,220
  • 1
  • 6
  • 20
0

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)
}
Silent observer
  • 79
  • 2
  • 13
0

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.

Rowanto
  • 2,819
  • 3
  • 18
  • 26
0

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.

callOfCode
  • 893
  • 8
  • 11