I am developing a library Next.js application. For the purposes of this question, I have two pages in my application: BooksPage which lists all books, and BookPage which renders details of a book. In terms of components, I have a <Books /> component which renders a <Book /> component for every book in my library database.
Here are my components:
Books.js:
function Books({ books }) {
return (
<>
{books.map(book => (
<Book key={book.id} book={book} />
))}
</>
);
}
Book.js:
class Book extends React.Component {
constructor(props, context) {
super(props, context);
this.state = { liked: false };
}
like = () => {
this.setState({ liked: this.state.liked ? false : true })
};
render() {
return (
<>
<Link href={`/books/${book.slug}`}>
<a>{book.title}</a>
</Link>
<Button onClick={this.like}>
<LikeIcon
className={this.state.liked ? "text-success" : "text-dark"}
/>
</Button>
</>
);
}
}
Problem:
Say that I am on page BooksPage. When I click the like button of a <Book /> the icon color toggles properly in the frontend and the like is successfully added or removed in the backend. When I refresh BooksPage all the state is maintained and consistent.
The problem arises when I like something on BooksPage and then immediately navigate to BookPage without refreshing using next/link. There the like button is not toggled consistently and the state from BooksPage is lost. Notice that if I hard-refresh the page everything goes back to normal.
Slow solution: Do not use next/link.
Replace
<Link href={`/books/${book.slug}`}>
<a>{book.title}</a>
</Link>
with
<a href={`/books/${book.slug}`}>{book.title}</a>
Fast solution: Keep using next/link?
Is there a way to use next/link and maintain state when navigating to another pre-rendered route?
TLDR: Any time the button needs to be changed, the API must change data, and it must update the closest parent's local state to update the button's appearance. The API will control all aspects of local state. You can't update local state unless an API request is successful. Therefore, the client and API are always 1:1.
The Button component in Book.js should NOT be maintaining its own state separately from the API data; instead, wherever you're fetching book data from the API, it should also be controlling the button's state (and its appearance). Why? Because with the current approach, the API request can fail, but the client will still update. As a result, the API and client may no longer be 1:1 (client shows liked, but API still shows that it's disliked/unliked).
In practice, you'll have a parent container that acts like a state manager. It fetches all relevant data, handles updates to the data, and displays the results using stateless child components. Any time a child component needs to be updated (such displaying a "like" or "dislike" button based upon a button press), it must first make an API request to change the data, then the API must respond with relevant data to the update the state used by the child:
Alternatively, if this component is reusable, then you'll conditionally render it using this.props.book (which comes from a parent container) or the child component must request data from an API to update its own local this.state.book. Same as the above diagram, the API requests control all aspects of state changes:
There's yet another approach that is the same as the diagram above, but it uses the child local state regardless of the parent's state. This child state will only be updated by its own class methods. This introduces a bit more complexity because you have to make sure that any parent state changes won't rerender the child component with stale data. Ultimately, which approach to take is up to you.
On a side note: This question doesn't make much contextual sense as libraries don't render pages nor do they attempt internal page navigations. Instead, they offer reusable components that can be utilized by one or many NextJS project pages or components.
You need to use Model.refresh_from_db(...)--(Django Doc) method to retrieve the updated value from the Database
class DeleteLikeView(APIView):
def post(self, request, book):
book = get_object_or_404(Book, id=book)
print(book.num_of_likes)
like = Like.objects.get(user=request.user, book=book)
like.delete()
book.refresh_from_db() # here is the update
print(book.num_of_likes) # You will get the updated value
return ...
Pass the liked property of a book somehow through the API. Then, pass that prop down from the Books component to the Book component.
Add a componentDidUpdate() method to your book component.
class Book extends React.Component {
constructor(props, context) {
super(props, context);
this.state = { liked: this.props.liked };
}
componentDidUpdate(prevProps) {
if (this.props.liked !== prevProps.liked) {
this.setState({
liked: this.props.liked,
});
}
}
like = () => {
this.setState({ liked: !this.state.liked })
};
render() {
return (
<>
<Link href={`/books/${book.slug}`}>
<a>{book.title}</a>
</Link>
<Button onClick={this.like}>
<LikeIcon
className={this.state.liked ? "text-success" : "text-dark"}
/>
</Button>
</>
);
}
}
In DeleteLikeView class you get book object. it retrieved from DB and saved in book variable.
when you deleted like object num_of_likes attribute has been updated in DB but your variable still consists previous object. after like.delete() command you should get object again and print num_of_likes att. It is as your expected.
Related
i am setting the language name in my local storage , when it changes from a dropdown in topbar , i want the whole current view to be re-rendered and words translated to the selected language. my layout is like this
render(){
return (
<MainContainer>
<TopBar/>
<SideBar/>
<RouteInsideSwitch/>
</MainContainer>
)
}
in render of components ,the words to be translated basically calls a function that returns the correct word based on the local storage language name.
i change the language and i set the state in maincontainer for selected langauge and set it in local storage. however i dont want to move that state from Maincontainer to all my components. also dont want to store it in redux because then all the possible containers have to listen to it and then pass it to their children as props.
what currently happens is that saving state in mainContainer without passing it to any children , the children does re-render but only the immediate ones , if there are more children in those children and so on , it does not re-render because i m not passing the state throughout the chain.
open to any suggestion based on different pattern for language changing. but my question is that is there any way to re-render the current open view (all components in dom).
If your concern is that you have a number of "possible containers" which all need to handle the state change, perhaps consider creating a higher order component that includes the common language rendering logic (your RouteInsideSwitch leads me to believe this may the issue). In that way, you can avoid duplicating that logic across a ton of "possible" components that all require the functionality of dynamic language rendering and will avoid the need to dial a bunch of components into a redux store, assuming they are in the same hierarchy.
const DynamicLanguageComp = RenderComponent => {
return class extends Component {
constructor(props) {
super(props)
//additional state setup if needed
}
changeLangFunc = () => { /* handle change */ }
render() {
return <RenderComponent handleLanguageChange={this.changeLangFunc} {...this.props} {...this.state} />
}
}
}
If you would like to avoid a re-render on certain intermediate components that may be receiving props by way of state change you can implement the lifecycle method shouldComponentUpdate(), which by default returns true. You can make a comparison of nextProps to your current props, and return false if a re-render is undesired despite new props.
I have two pages (page1 and page2) with a SimpleTable component.
I have a page with a table for apples:
render() {
const props = this.props
return (
<Page {...props}>
<SampleTable requestData={this.getApples} columns={[<columns for apples>]} />
</Page>
)
}
And a page with a table for tomatoes:
render() {
const props = this.props
return (
<Page {...props}>
<SampleTable requestData={this.getTomatoes} columns={[<columns for tomatoes>]} />
</Page>
)
}
For reasons unknown to me, this particular child (SampleTable) is not being unmounted / mounted when I transition from page1 to page2 or vice-versa.
It is strange, since all the other children in all the other pages are being mounted / unmounted, even when I am using the same component for the children. So, clearly, I do not understand how / when does React decide to mount / unmount, or when does it decide to reuse a component. But I digress.
I will accept this fact: React will reuse children whenever it pleases.
My problem is that I am using Sampletable.componentDidMount to request data:
componentDidMount() {
console.log('SampleTable did mount. Requesting data ...')
this.props.requestData(state.pageSize, state.page, state.sorted, state.filtered).then(res => {
this.setState({
data: res.rows,
pages: res.pages,
loading: false,
})
})
}
But the function to request the data is provided by the parent, via props. I have the following problem:
on initial rendering, page1 provides a requestData=getApples via props to a SampleTable child.
SampleTable mounts and requests the data, using the getApples method provided by page1
I move to page2
page2 provides a different requestData=getTomatoes method to a SampleTable child
somehow Reacts decides that it can reuse the original component for this child and, since it is already mounted, componentDidMount is not called
and therefore, the new getTomatoes method is not called, and no data suitable for the tomatoes table is requested.
What am I doing wrong?
How can I ensure that the change in props is triggering a data reload?
You should try using a unique key attribute on your SampleData component while calling it, so that react knows it a different instance of your component and re-render it accordingly.
You can find more about keys here: https://reactjs.org/docs/lists-and-keys.html
Let's say I have a nested URI structure, something like the following:
http://example.com/collections/{id}
http://example.com/collections/{collectionId}/categories/{id}
http://example.com/collections/{collectionId}/categories/{categoryId}/book/{id}
I can use react-router to render the correct component on page load, and when the URI changes.
Let's take the first case:
http://example.com/collections/{id}
Let's assume we have a CollectionShow component.
When the component first loads, I can pull the collection ID out of the URI and load the correct collection:
componentDidMount () {
this.loadCollection(this.props.match.params.id);
}
(Assume that loadCollection loads a collection with an AJAX call and sets it into the component's state.)
However, when the URI changes (through, e.g., the user clicking on a <Link>, react-router doesn't entirely re-build the component, it simply updates its props, forcing it to rerender. So, in order to update the compomnent's state, we also need to update the state on update:
componentDidUpdate(prevProps) {
if (!this.state.collection || this.collectionDidChange(prevProps)) {
this.loadCollection(this.props.match.params.id);
}
}
collectionDidChange(prevProps) {
return String(prevProps.match.params.id) !== String(this.props.match.params.id)
}
So far so good. But what about the second URL?
http://example.com/collections/{collectionId}/categories/{id}
Let's assume we have a CategoryShow component.
Now we don't only have to consider the collectionId changing, but also the category ID. We have to reload the collection if that ID changes, and we also have to reload the category if that changes.
The problem compounds with a third-level nesting (a BookShow component). We end up with something like this:
componentDidUpdate(prevProps) {
if (!this.state.collection || this.collectionDidChange(prevProps)) {
this.loadCollection(this.props.match.params.collectionId);
}
if (!this.state.category || this.collectionDidChange(prevProps) || this.categoryDidChange(prevProps)) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId);
}
if (!this.state.book || this.collectionDidChange(prevProps) || this.categoryDidChange(prevProps) || this.bookDidChange(prevProps)) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId)
.then(() => this.loadBook(this.props.match.params.id);
}
}
Not only is this unwieldy, it also results in a fair amount of code duplication across the three components, CollectionShow, CategoryShow and BookShow.
Using redux won't help matters much, because we still have to update the global state when the URI changes.
Is there a clean, efficient, React-friendly way of handling updates of nested resources such as these?
You could create a CollectionPage component that handles all the AJAX calls and keeps data in state.
This could pass down the collection, category/categories and books to the components (CollectionShow, CategoryShow and BookShow).
In CollectionPage you could use componentDidUpdate and componentDidMount as you presented it.
Your <*>Show components will know nothing about props.match.params.* and will only get the data needed to render the wanted content.
CollectionPage can be use for all your routes or you could change the route to something like
/collections/:collectionId?/:categoryId?/:bookId?
making all params options. You can check for the available ids in CollectionPage.
Hope it helps!
If I understood your problem it is something architectural. The parent component is the one that should be doing this management and injecting the result through subcomponents. Split your component in small components and render each one accordingly.
The code you shared will be splint in 3 others
The mponentDidUpdate(prevProps) method will go to the parent component simply as a componentDidMount().
Then if the router changes the component will be recreated and the new values will be sent across the modules.
If you dont wanna split you code you should at least do the step 2.
//everytime you get to the router this will be triggered and depending of the parameters of your router, you get the values you need and set the state
componentDidMount() {
if (!this.state.collection) {
this.loadCollection(this.props.match.params.collectionId);
}
if (!this.state.category) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId);
}
if (!this.state.book) {
this.loadCollection(this.props.match.params.collectionId)
.then(() => this.loadCategory(this.props.match.params.categoryId)
.then(() => this.loadBook(this.props.match.params.id);
}
}
render() {
return (
//you can add conditions to render as well
<CollectionComponent {...this.props} {...{
collection: this.collection
}} />
<CategoryComponent {...this.props} {...{
categ: this.categ
}} />
<BookComponent {...this.props} {...{
book: this.book
}} />
)
}
I am encountering several issues in a very basic color harmony picker I am developing. I am still a beginner in React and JSX. I initially had it put up on GitHub so the full files are on there, but I moved it over to Codepen instead.
Here is the Codepen
I made a lot of comments so sorry if they're a bit much, but hopefully they help. My problems don't begin until line 41, the displayHarmonies() method of the DataStore class. The values passed to it come from my App (parent) component:
displayHarmonies(color, harmony) {
//color and harmony pass in dynamically just fine...this.data will not return anything, not even "undefined"
console.log(color + " is the color and " + harmony + " is the harmony...and dataStore.displayHarmonies says: " + this.data);
this.registeredWatchers.map((watcher) => {
let result = "not green"; //result and resultHex will be determined with an underscore statement that will associate the color & harmony choice (primary + foreign key concept) and will return correct harmony color(s)
let resultHex = "#HEX";
appState.harmonyColor = result;
appState.harmonyHex = resultHex;
//call to app component's onDataChange() method, where new states will be set using the the appState data we just set in lines 49 and 50
watcher.onDataChange();
})
}
As you can see from my first comment, the only part that doesn't log to the console is this.data, which is set in the constructor for the DataStore:
constructor(data) {
//store that data in the object
//data is not being received from object instance of dataStore on line 187
this.data = data;
On line 187 I make an instance of the DataStore and pass it a variable named data. Prior to being used, this variable is initialized and then assigned to parsed JSON data via Fetch API:
let data = [];
//use polyfill for older browsers to do Ajax request
fetch("data/data.json").then((response) => {
//if we actually got something
if (response.ok) {
//then return the text we loaded
return response.text();
}
}).then((textResponse) => {
data = JSON.parse(textResponse);
});
If I console out the data in the second fetch .then() method, the JSON comes back just fine. As soon as I try to use the data variable anywhere else in the application, it returns nothing, as shown in the displayHarmonies() method's console.log(). So that's my first issue, but before I wanted to get to that, I wanted to solve the other issue I was having.
After the appState object (initialized prior to the DataStore, under the fetch statement) values get set to the result variables, displayHarmonies() runs watcher.onDataChange() (in the App component/parent) where the harmonyColor and harmonyHex states get assigned to the new appState values:
onDataChange() {
console.log("onDataChange() in App called");
this.setState({
harmonyColor: appState.harmonyColor,
harmonyHex: appState.harmonyHex
})
}
If I log these states out to the console, they are the right values, so that's not the problem. I then pass my states to the Display child component to be used as properties:
<Display colorChoice={this.state.currentColor} harmonyChoice={this.state.currentHarmony} harmonyColor={this.state.harmonyColor} harmonyHex={this.state.harmonyHex} />
I then set the Display component states in the constructor, assigning them to the props that are being sent to it with each new rendition of the application. I then display the data onto the DOM with the Display component's render method. What's odd is that the application will display the initial states (color: red, harmony: direct, harmonyColor: green, etc.) just fine, but as soon as a change is made, the data on the DOM does not update. The initial data is loaded in the same way though: by passing the parent's states into the child's properties. I have a few console.log()s in place that seem to prove why this should work, however, it does not. So what am I doing wrong?
Thanks, and hope this is not too much for one question!
First a bit to your current code, at the end of the post, I have added an alternative solution, so if this is tl;dr; just skip to the snippet at the end :)
A first remark would be on the data variable that you wish to pass on to your DataStore, nl (I left out some parts, as they are irrelevant to the discussion)
let data = [];
fetch("data/data.json").then(( response ) => {
data = JSON.parse( response.text() );
});
//... later down the code
var store = new DataStore(data);
Here you are reassigning the data variable inside the then promise chain of your fetch call. Although the assignment will appear to work, the data that now is on store.data will be an empty array, and the global variable will data will now contain the parsed response.text(). You should probably just push in the data you have just parsed (but in my example, I didn't even include the DataStore so this is just for future reference)
In your CodePen, you seem to mixing props & state for your Display component. That is in essence a no-op, you shouldn't mix them unless you really know what you are doing. Also note, that by calling this.setState inside the componentWillReceiveProps life cycle method, the app will automatically re-render more than needed. I am referring to this code:
componentWillReceiveProps(nextProps) {
this.setState({
color: nextProps.colorChoice,
harmony: nextProps.harmonyChoice,
harmonyColor: nextProps.harmonyColor,
harmonyHex: nextProps.harmonyHex
});
}
But you are then rendering like this:
render() {
return (
<div>
{/* these aren't changing even though states are being set */}
<p><b>Color:</b> {this.state.color}</p>
<p><b>Harmony:</b> {this.state.harmony}</p>
<p><b>Harmony Color(s):</b> {this.state.harmonyColor} ({this.state.harmonyHex})</p>
</div>
)
}
Here you should remove the componentWillReceiveProps method, and render values from this.props as you are passing these along from your App.
Alternative solution
As mentioned in the comments, your code currently is doing a lot more than it should do to pass state between parent and child components.
One thing you should keep in mind, is that when a component state gets changed, react will re-render the component automatically. When it sees that the virtual DOM has discrepancies with the real DOM it will automatically replace those components.
In that sense, your DataStore is not necessary. Depending on how you want to manage state, the component will react on those changes.
Since your app uses Component State (which is fine for small applications, once you want to move to bigger applications, you will probably want to move on to something like Redux, or MobX), the only thing you need to do, is to make sure that you set the correct components state to trigger the rendering.
As an example, I remade your code in a cleaner way:
const Choice = ({ header, values, onChange, activeValue }) => {
return <ul>
<li><h1>{ header }</h1></li>
{ values.map( (value, key) => <li
key={key+value}
className={classNames( { active: value === activeValue, item: true } )}
onClick={() => onChange( value )}>{ value }</li> ) }
</ul>
};
const colors = ['red', 'green', 'black', 'blue', 'yellow'];
const harmonies = ['direct', 'split', 'analogous'];
class App extends React.Component {
constructor(...args) {
super(...args);
this.state = {
activeColor: undefined,
activeHarmony: undefined
};
}
onColorChanged( color ) {
this.setState({ activeColor: color });
}
onHarmonyChanged( harmony ) {
this.setState({ activeHarmony: harmony });
}
render() {
let { activeColor, activeHarmony } = this.state;
return <div>
<Choice
header="Choose color"
values={colors}
activeValue={activeColor}
onChange={(...args) => this.onColorChanged(...args)} />
<Choice
header="Choose harmony"
values={harmonies}
activeValue={activeHarmony}
onChange={(...args) => this.onHarmonyChanged(...args)} />
</div>;
}
}
ReactDOM.render( <App />, document.querySelector('#container'));
h1 { margin: 0; padding: 0; }
ul {
list-style-type: none;
}
.item {
cursor: pointer;
padding: 5px;
}
.active { background-color: lightgreen; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
Now, there are some things in this sample code that might need some explanation. For one, this code has 2 component types, 1 presentational component called Choice which is stateless, and one container component called App which delegates it's state to it's children.
A bit more information about container & presentational components can be found on the blog of Dan Abramov (redux creator)
The essence of the above concept is just this, the App component is responsible for the state, and for sharing it with it's children. So, all state changes need to be made on the App component. As you can see in the render, the App simply passes its state along:
render() {
let { activeColor, activeHarmony } = this.state;
return <div>
<Choice
header="Choose color"
values={colors}
activeValue={activeColor}
onChange={(...args) => this.onColorChanged(...args)} />
<Choice
header="Choose harmony"
values={harmonies}
activeValue={activeHarmony}
onChange={(...args) => this.onHarmonyChanged(...args)} />
</div>;
}
The App passes a change handler along to the Choice component that can be called when a selection should occur, this gets forwarded to the App, the state changes, and app re-renders, allowing the Choice component to update it's elements.
const Choice = ({ header, values, onChange, activeValue })
Based on the props passed into it, the Choice component can decide which is the active item at the moment of rendering. As you can see, the props are destructed. header, values, onChange and activeValue are all properties on the props of the component, but to save time, we can assign these values at ones to a variable and use them in the rendering.
I tried cloning your repo, but it seems to be nested in another repo. With your current setup, this may work:
In your App component, you can put this lifecycle method to fetch the data, and then set the state with the received data.:
componentDidMount(){
fetch("data/data.json").then((response) => {
//if we actually got something
if (response.ok) {
//then return the text we loaded
return response.text();
}
}).then((textResponse) => {
this.setState({
data : JSON.parse(textResponse);
})
});
}
In the return statement, you can render the data store as a child so App can pass the data like this:
return (
<div className="App">
<DataStore data={this.state.data} />
<h1>Color Harmonies</h1>
{/* assigns this.colorChosen() & this.harmonyChosen() methods as properties to be called in Picker component */}
<Picker colorChosen={this.colorChosen.bind(this)} harmonyChosen={this.harmonyChosen.bind(this)}/>
{/* give Display component props that are dynamically set with states */}
<Display colorChoice={this.state.currentColor} harmonyChoice={this.state.currentHarmony} harmonyColor={this.state.harmonyColor} harmonyHex={this.state.harmonyHex} />
</div>
);
Then, your data store should receive the data as a prop, so you can use it like this:
displayHarmonies(color, harmony) {
//color and harmony pass in dynamically just fine...this.data will not return anything, not even "undefined"
console.log(color + " is the color and " + harmony + " is the harmony...and dataStore.displayHarmonies says: " + this.props.data); //data is received in the properties so you can use it.
//other code
})
Doing this, you should also be able to remove this.data from the constructor of the DataStore component.
Also in Data store, youll want to to allow it to accept props like this:
constructor(props){
super(props)
}
I need to render a widget which takes in settings from a SettingsPanel and passes them to a LayoutPanel (which would then re-render itself based on the updated settings). I can't seem to figure a 'clean' way to do this. Here's what I have so far:
Widget.js
class Widget extends Component {
handleSettingsChange() {
//need to let layout know of change
}
render() {
<div>
<SettingsPanel
onSettingsChange={handleSettingsChange}
initialSettings={this.props.settings}
/>
<Layout
data={ this.props.data}
/>
</div>
}
}
App.js
const settings = {
hideImages: true,
itemsPerPage: 5
}
<Widget settings={ settings } data={ data } />
My first thought was that, within Widget, I could do
constructor() {
super()
this.setState({ settings: this.props.settings });
}
handleSettingsChange(data) {
//Assuming data is of the form {changedProperty: value}
this.setState({ settings: Object.assign({}, this.state.settings, data) });
}
render() {
<div>
<SettingsPanel
onSettingsChange={handleSettingsChange}
initialSettings={this.props.settings}
/>
<Layout
data={ this.props.data}
settings={ this.state.settings }
/>
</div>
}
This way the parent is a dump message broker, and doesn't not have to know the specifics of what specific settings are being change. But of course, this doesn't work because,
a. I'm told setting state from props is an anti-pattern
b. The constructor doesn't have access to this.props anyway, and doing it else-where (componentDidMount?) feels wrong.
My second thought was to go to setProps to set props on children from the parent, but of course that's deprecated.
How do I solve this? I could probably re-architect this so this isn't a problem, but frankly I'd just like to understand what I'm missing, or what the 'react' way of solving things like this is.
This question is similar to the same problem I'm having but I'm having trouble understanding/applying what the accepted solution suggests to my problem.
tldr; "How can I make a parent component pass state from one child to another, while being ignorant of what exactly it's passing?"
While lukewestby's answer is correct in that it solves the problem in the original question, after further reflection I realized that that was the wrong question to ask - i.e., it was an architecture issue which is why I was having trouble with the implementation.
In this case the data-flow is [data]-> App -> Widget -> Children. "App" fetches data, so App owns it. Widget should not be able to to modify, handle changes to data it doesn't own. Hence, the callback needs to be passed to the grandparent (App), not the parent (Widget). Here's what I ended up doing.
App.js
constructor() {
this.state = { settings: { hideImages: true } };
}
handleSettingsChange(data) {
this.setState({ settings: data });
}
render() {
<Widget settings={ this.state.settings }
onSettingsChange={ this.handleSettingsChange }
/>
}
Widget.js
handleDisplaySettingsChange(data) {
this.props.onSettingsChange(data);
}
render() {
return (
<div>
<SettingsPanel
onSettingsChange={ this.handleDisplaySettingsChange }
/>
<Layout
{...this.props.settings }
/>
</div>
);
}
The advantage of doing it this way is that there is a single source of truth for settings, which is maintained by the owner (App). In the original suggestion, if on a later fetch by App the settings had changed the inner components wouldn't receive it (which explains why the React docs claim setting state from props is an anti-pattern)
It's not necessarily true that setting state from props is an anti-pattern. It seems completely reasonable to initialize a component's state from props in the constructor. Doing so doesn't degrade testability or reliability much if at all.
You can access props in the constructor as they are passed as the first parameter. You'll need to call super first, but then you can do whatever you'd like:
class Widget extends React.Component {
constructor(props) {
super(props);
this.setState({ settings: props.settings });
}
}
Then you can continue down the path you were originally headed. Another slightly more complex option would be to use a state container like Redux to handle state management and transitions for you, but only do this if you don't mind the additional learning and complexity of adding more to your app setup.