We're working on an admin portal. We've got a user account page, where the user will be able to edit their personal info and eventually stuff like security, privacy, notifications, etc.
Note: We're using classes and not functions. Let's skip the questions on "why" or trying to "sell me this pen" on using functions over classes.
Note: Also, for reference, we're using https://mui.com. Just in case you need to know.
We've got a page template for all portal pages, called <PortalTemplate/>.
On the PortalTemplate, we are using a cookie, to execute a getUser() function that returns user details. This is captured in this.state.account.
The user's account page is injected as this.props.children.
// PORTAL TEMPLATE (basic):
export interface PageProps {
children: any;
pageTitle: string;
}
export interface PageState {
account: AccountDTO;
anchorEl: HTMLElement | null;
isLoginActive: boolean;
isMenuOpen: boolean;
}
export default class PortalTemplate extends React.Component<PageProps, PageState> {
render() {
return(
<div>
{this.props.children}
</div>
);
}
}
This portal template is used for ALL portal pages, as sort of like a wrapper that contains the global menu stuff. We're trying to determine how to actually leverage the state variables, but also sending this down to children component pages so they can use the same details to render dynamic data.
// ACCOUNT PAGE (basic):
export default class AccountPage extends React.Component<PageProps> {
constructor(props: PageProps) {
super(props);
}
render() {
return (
<PortalTemplate pageTitle='Account'>
{ page content here }
</PortalTemplate>
)};
}
Our problem is that we're having trouble sending the this.state.account data down from PortalTemplate to AccountPage so that the user's account page can use these details to render the data, but also have a form where they can edit their details.
React Ref:
https://beta.reactjs.org/learn/sharing-state-between-components#step-3-add-state-to-the-common-parent
We tried using this reference, but are finding it hard to follow. Since our PageTemplate component uses { this.props.children } to allow components to be used "under" it, we're not able to figure out where this.state.account is supposed to go in order to be utilized by child pages/components.
We've also tried to clone the child, like other posts have depicted, but was unsuccessful.
{React.cloneElement(this.props.children)}
Related
I am relatively new to ReactJS and I'm building an app with a Context. At some point I need to get one of the values from the Context, make it available for editing and only after it's submitted change it's value in the Context (to avoid refreshing all the other components). I thought of doing something like this:
export default class AComponent extends Component<any, { property?: Property }> {
public constructor(props: any) {
super(props);
}
public shouldComponentUpdate(nextProps: any, nextState: { property?: Property }) {
return nextState.property !== this.state.property;
}
public render(): JSX.Element {
return (
<AContext.Consumer>
{(data) => {
// ...
this.setState({ ...this.state, property: data.property });
// ...
}}
</AContext.Consumer>
);
}
}
But even with the shouldComponentUpdate check, it still refreshes 4 times! Without it it produces an error (recursion limit reached or something like that).
The question is: What's the correct way of working with a context property without changing it until the moment you actually want to?
I wanted to get the Context property inside the constructor but I have found that this form is deprecated:
constructor(props: any, context: AContext) {
super(props, context)
}
Is there any other way?
not only can property add to context but you can also add methods to them.
Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language
if you want to change data from different components, I prefer to use state management like Redux
happy codeing
I have a problem with ReactJS as when in parent component's state that stores child components (as thumbnails) the child components stored as array of components are constructed once and have componentDidMount called once. Once I 'reimport' data and create new child components based on new data from backend API (for example upon new sorting mode activated) the components do not call componentDidMount and i have to rely on componentWillReceiveProps method to import for example a link to the picture to be displayed on a thumbnail (it seems like react reuses components). If for example the the data in child components is being imported slowly it shows and old photo because remembers previous iteration done in own componentDidMount done after creation.
How can i force react to always create new child components from the scratch and thanks to that achieve having componentDidMount called to include data import from backend and avoid relying on componentWillReceiveProps call?
Here is the pseudocode where parent component ComponentManager imports person data from backend and creates thumbnails based on retrieved JSON. Thenafter it can upodate thumbnails after user changes sorting order:
class ComponentManager extends Component {
constructor(props) {
super(props);
this.state = {
personsThumbnails : undefined
}
}
componentDidMount() {
// Import person ids and create SinglePersonThumbnail(s) child components as the state personsThumbnails
importPersonsIds();
}
importPersonsIds(sortingMode) {
// Importing persons data from backend API and created thumbnails stored in personsThumbnails state
...
}
render() {
return(
<div>
<button onClick={()=>{this.importPersonsIds("SORT_BY_AGE")}}>Sort by age</button>
<button onClick={()=>{this.importPersonsIds("SORT_BY_NAME)}}>Sort by name</button>
<div>
{this.state.personsThumbnails}
</div>
</div>
);
}
}
class SinglePersonThumbnail extends Component {
constructor(props) {
super(props);
this.state = {
photoUrl : undefined,
personsName : undefined
}
}
componentDidMount() {
// Called when component is created
this.importDataAndPhotoForPerson(this.props.id);
}
componentWillReceiveProps(nextProps) {
// Called always when ComponentManager changes the order of thumbnails upon other sorting mode triggered
this.importDataAndPhotoForPerson(this.props.id);
}
importDataAndPhotoForPerson(id) {
// Imports name of the person and link to photo stored in state
}
render() {
return(
// Display image by link and person's name based on photoUrl and personsName states!
);
}
}
you can use componentDidUpdate() and can see the documentation from official site.
componentWillReceiveProps is outdated and not recommended. A better idea will be to store the data from backend in some context/redux. This will cause the child components to re-render with updated data.
Search.js
class Search extends Component {
constructor() {
super();
this.state = {
selectedPictures: []
}
}
static getSelectedPictures = () => {
return this.state.selectedPictures;
}
render() {
return (
<div>
...
</div>
);
}
}
export default Search;
Other.js
import Search from './Search';
class Other extends Component {
constructor() {
super();
this.state = {
}
}
render() {
console.log(Search.getSelectedPictures); --> Uncaught null
return (
<div>
...
</div>
);
}
}
export default Other;
How to call Search.state.selectedPictures inside Other.js?
I already try to use static method to return this.state.selectedPictures and call in Other.js, but cannot access.
Any way can import or transfer the var? Both js files are separate files
Thank you.
What you're trying to do isn't really possible in React for a couple of reasons. First of all, you're trying to call methods and access properties on a class, not on an object. You would, in normal (modern) JS, be required to instantiate the class with the new keyword. For example, search = new Search(); search.getSelectedPictures() - this, however, isn't really how React works, and because your classes are actually components, you have to use the <Search/> component syntax in your render function.
As for getting access to the state in Search, you'd need to pass that state from Search to Other.
One way would be to pass the state into the props directly, so in search.js:
render() {
<Other selectedPictures={this.state.selectedPictures} />
}
Then in other.js:
render() {
this.props.selectedPicture.forEach((pic) => <img src={pic} />);
}
Alternatively, you could have a more umbrella parent component, and keep the state in there. Then pass that state to both components simultaneously, if the ones you list are not meant to have a parent-child relationship.
There are also, albeit slightly more complex, ways of doing what you wish but with Search as a child of Other, but without knowing what those two components actually are, it's hard to really tell.
Use flux architecture . The simple implementation is
alt flux
Just create an Action and a Store . When you select images just put them in the Store using Action then get them as props using <AltContainer />
I am creating a reactJS app that will show a list of mobile phones. I am currently calling an API, setting state to the response, and then displaying the contents. I am now looking to add sort order to this but am having difficulty.
When the user goes to /phones/Apple/iPhone, the routing will render the following component that calls the api, sets the state and passes the data to the results component...
export default class Phones extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
make: this.props.params.make || null,
model: this.props.params.model || null
};
}
componentDidMount(){
const makeQuery = this.state.make ? this.state.make + '/' : '';
const modelQuery = this.state.model ? this.state.model : '';
const fetchUrl = '/api/phones/' + makeQuery + modelQuery;
fetch(fetchUrl, {
headers: {
Accept: 'application/json'
}
}).then(response => {
if (response.ok) {
response.json().then(json=> {
this.setState({data: json});
});
}
});
}
render() {
if (this.state.data) {
const currentUrl = this.props.location.pathname;
return (
<section className="phones">
<Filters
data={this.state.data}
currentUrl={currentUrl} />
<Results
data={this.state.data} />
</section>
)
}
}
}
The Results Component will map through the data and render the list of phones.
In the Filters Component I then have a dropdown that allows user to sort the results by price value, and this sets the state too.
export default class Filters extends Component {
constructor(props){
super(props);
this.state = {
value: 0
}
this.onChange = this.onChange.bind(this);
}
onChange(e){
this.setState({value: e})
}
render() {
return (
<div>
<p>Price</p>
<select onChange={this.onChange}>
<option value='asc'>low to high</option>
<option value='desc'>high to low</option>
</select>
</div>
);
}
}
The problem I'm having is that I do not know how I can apply this filter to the component which renders the results, and what is the best way to go about this?
I have started reading in redux, but am confused if the phones data and filters should be in a store as it is temporary and will change if they go to another page i.e /phones/Samsung/Galaxy/
Any help is appreciated
Typically in React, a parent container will handle state for all sub-components - data and props flow in one direction, down through the component tree.
Redux offers a way to give containers access to a method for updating state.
This page from the Redux docs provide instructions for how to integrate with React.
Redux provides a single object that contains the entire application state. Then an additional NPM module, react-redux, provides two functions for "connecting" a component to global Redux state: mapStateToProps and mapDispatchToProps.
Using this approach, you can have your Filters container set a toggle in global state:
state = {
phones: [],
sortBy: 'price',
sortOrder: 'asc',
}
Or similar. You can then use mapStateToProps to gain access to the sort state slices, and mapDispatchToProps to "dispatch" actions that update the state.
The Redux docs are excellent, and written with simplicity and beginners in mind.
So there are a couple of ways to do this. You mentioned you're just beginning to look into Redux, and I'd encourage you to keep on that path. It will help you much in the world of application state management. That said, if you are doing this with just React:
The parent component is Phones, so write a helper method in Phones to keep track of the filter (make sure you set a default filter state for when the component first constructs or mounts like you did with make and model):
setFilter(filter) {
this.setState({filter});
}
Pass the filter from the Phones component state to both the Filters and Results components. Also pass the setFilter class method into the child Filters component (All this is done in your Phones component's render method):
return (
<section className="phones">
<Filters
filter={this.state.filter}
onChange={this.setFilter}
data={this.state.data}
currentUrl={currentUrl} />
<Results
data={this.state.data}
filter={this.state.filter}/>
</section>
)
Change your Filter component so that its onChange event is the setFilter handler you passed into it, and the value prop in the <select> component is the filter we passed in (2).
I'll leave 3 to you. I think you can figure it out :). Note that you now have access to the filter value as a prop in your results class and you can sort in that component.
I have started reading in redux, but am confused if the phones data and filters should be in a store as it is temporary and will change if they go to another page i.e /phones/Samsung/Galaxy/
Redux is a great solution to this problem. By externalising the state, both components can easily access the necessary values.
Your concern about the data changing should not be so, as this is an important and common situation for redux stores. Even the todo list example from their documentation implements a filter component similarly to what you are describing.
Another benefit is that if you use the redux-thunk middleware (or whichever async middleware you want), you can also move your fetch call out of the component so it becomes a little bit simpler.
There are plenty of examples floating around for this kind of setup so I won't bother writing any out for you now, but I'll update this answer for any specific requests in the comments.
Alternatively, you can use callbacks to pass data up and down between components.
This image from this blog has is a great illustration of how this can be achieved
I using Meteor 1.3 for this application together with react js and Tracker React.
I have a page to view all available users in the application. This page require user to login to view the data. If user not logged in, it will shows the login form and once logged-in the component will render the user's data.
Main component for the logic.
export default class MainLayout extends TrackerReact(React.Component) {
isLogin() {
return Meteor.userId() ? true : false
}
render() {
if(!this.isLogin()){
return (<Login />)
}else{
return (
<div className="container">
<AllUserdata />
</div>
)
}
}
}
And in the AllUserdata component:
export default class Users extends TrackerReact(React.Component) {
constructor() {
super();
this.state ={
subscription: {
Allusers : Meteor.subscribe("AllUsers")
}
}
}
componentWillUnmount(){
this.state.subscription.Allusers.stop();
}
allusers() {
return Meteor.users.find().fetch();
}
render() {
console.log('User objects ' + this.allusers());
return (
<div className="row">
{
this.allusers().map( (user, index)=> {
return <UserSinlge key={user._id} user={user} index={index + 1}/>
})
}
</div>
)
}
};
The problem is when logged in, it only shows the current user's data. All other user objects are not rendered. If I check on the console, console.log('User objects ' + this.allusers()); show objects being rendered 3 times: the first render only shows the current user's data, the second one renders data for all users (the desired result), and the third one again renders only the current user's data.
If I refresh the page, the user data will be rendered properly.
Any idea why?
React calls the render() method of components many times when it's running. If you're experiencing unexpected calls, it's usually the case that something is triggering changes to your component and initiating a re-render. It seems like something might be overwriting the call to Meteor.users.find().fetch(), which is probably happening because you're calling that function on each render. Try inspecting the value outside of the render method or, better yet, rely on tests to ensure that your component is doing what it should be :)
From https://facebook.github.io/react/docs/component-specs.html#render
The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it's invoked, and it does not read from or write to the DOM or otherwise interact with the browser (e.g., by using setTimeout). If you need to interact with the browser, perform your work in componentDidMount() or the other lifecycle methods instead. Keeping render() pure makes server rendering more practical and makes components easier to think about.
See also:
https://facebook.github.io/react/docs/advanced-performance.html
https://facebook.github.io/react/docs/top-level-api.html#reactdom
https://ifelse.io/2016/04/04/testing-react-components-with-enzyme-and-mocha/