I have a users component that just displays a list of users. I have tried to wrap it in a HOC loading component so that it only displays once the users are loaded, otherwise shows a loading spinner (well just text for now)
this is my HOC:
const Loading = (propName) => (WrappedComponent) => {
return class extends React.Component{
render(){
return this.props[propName].length === 0 ? <div> Loading... </div> : <WrappedComponent {...this.props} />
}
}
}
export default Loading;
at the bottom of my users component I have this:
export default connect(
mapStateToProps,
mapDispatchToProps
)(Loading('users')(Users));
currently, the word Loading... is just staying on screen. and propName is coming through as undefined
I think for some reason the users component is never getting populated. what have i done wrong?
Update after comments
My answer below is a misleading one since I hadn't understood your intention properly at that time. Also, my explanation about not getting props is somehow wrong. It is true if we don't render the components but here you are doing it. So, the problem was not that.
The problem here is your Loading component isn't rendered again after fetching users. Actually, you never fetch the users :) Here are the steps of your app (probably).
You are exporting a HOC function, not the Wrapped one here. It comes from your Users file but it does not export the real Users component. This is important.
Your parent renders the first time and it renders the exported HOC component.
Your child component renders and fall into Loading one not the Users one.
In Loading your users prop is empty, so you see Loading....
Your Users component never renders again. So, fetching the users there don't update the state.
Your solution is extracting the fetch out of Users and feed this component. Probably in a parent one. So:
Parent fetches the users then renders itself and all its children.
Your Loading HOC component renders a second time.
I don't know how do you plan to use this HOC but if I understood right (since I'm not so experienced with HOC) in your case the problem is you are not passing any prop to the Loading function. This is because you are not using it as a regular component here. It is a function and propName here is just an argument.
When we render a stateless function like this:
<Loading propName="foo" />
then there will be a props argument for our function. If we don't render it like that there will be no props argument and no props.propName. If this is wrong please somebody fix this and explain the right logic. So, you want to do something like this probably:
class App extends React.Component {
render() {
return (
<div><FooWithLoading /></div>
);
}
}
const Loading = (users) => (WrappedComponent) => {
return class extends React.Component {
render() {
return (
users.length === 0 ? <div> Loading... </div> :
<WrappedComponent
users={users}
/>
);
}
}
};
const Foo = props => {
return (
<div>
Users: {props.users}
</div>
);
}
const FooWithLoading = Loading("foobar")(Foo);
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
So in your case:
export default connect(
mapStateToProps,
mapDispatchToProps
)(Loading('users')(Users));
should work?
Or you need to render your component properly in a suitable place of your app.
Related
I want to pass a simple string, number or boolean up more than one level in my component tree. From what I read I need to do this with callback functions but I can't seem to get the logic right.
Here is a sample of where I pass a prop down from Parent App to grandchild Breadcrumb. I would like this prop to actually come from the last child in the tree, the "ResultsPage" component.
I realise there are better ways of doing sth like this (redux, context, different structure, etc), the point here for me is learning and to understand how to use callback functions and how to pass a prop up several more than 1 level.
Newbie friendly please - thanks for any input :)
class App extends React.Component {
render() {
return (
<>
<h1>Top level app</h1>
{/* I import the header and pass down prop */}
<Header currentLocation="Results Page" />
{/* I import the main app content */}
<ResultsPage />
</>
);
}
}
function Header(props) {
return (
<>
<h2>
This is the header element. It will have some nav items and the
breadcrumb I import
</h2>
{/* I import the breadcrumb accept the props from parent and pass the props down to child */}
<Crumbs currentLocation={props.currentLocation} />
</>
);
}
function Crumbs(props) {
return (
<>
{/* I display the props I passed down through the tree */}
<h3>
<small>This is the breadcrumb, you are on</small>{" "}
{props.currentLocation}
</h3>
</>
);
}
function ResultsPage() {
return (
<>
<p>
This is the actual results content. I would like this component to tell
the header component that I have loaded so it can update the breadcrumb
to let it know which page is currently loaded in the app.
</p>
</>
);
}
export default App;
To complete this issue I lewave the following solutions:
Codesandbox: Solution to the initial question
Codesandbox: Additional solution for the same problem using only functional components
Hope it helps the next guy :)
Maintain a local state variable to store the location, and pass a callback function through the props to set it.
class App extends React.Component {
constructor(props){
super(props);
this.state = {
currentLocation : "InitialLocation"
}
}
changeCurrentLocation = (newLocation) => {
this.setState({currentLocation : newLocation})
}
render() {
...
<ResultsPage callback={this.changeCurrentLocation}/>
}
}
The changeCurrentLocation function takes the new location as argument and modifies the state. Everytime the state changes, the render function is called again. This would refresh the view with updated state information, in your case - currentLocation.
function ResultsPage({ callback }) {
useEffect(() => {
callback('My Results');
}, [callback])
return (
...
);
}
Best way is to do this as I think keep the state inside a redux store. You can create a listener using subscribe() method in the redux to listen any dispatches from the child components from the parent component.
Also there is some easy method, You can use localstorage. You can store value from the child component and listen it by the parent component using window.addEventListener('storage', function(e) { } callback method. I hope you can understand what I tried to say.
I would like to be able to have a component whose rendering function is in another file in order to have a separation between the logic of my component and the rendering.
Naively, I tried to do just one file containing my component and which rendered a functional component of the same name to which I passed the necessary props so that everything was displayed correctly.
Something like that :
// MyComponent.render.jsx
export default MyComponentRender = (props) => {
return {
<View>
// render all components of my view
</View>
}
}
// MyComponent.js
class MyComponent extends Component {
// some logic
render() {
return (
<MyComponentRender
aLotOfProps=....
/>
)
}
}
But I soon found myself having to send, sometimes, a fairly large amount of props and +, I have for example textInputs that need to be focus() or blur() in reaction to some logic in my view but as a result, I couldn't control that just by sending props. It quickly became a mess!
I was wondering if there was a simple way to separate the logic of a component and its rendering function? Maybe there is a way to pass the context of my component to my rendering function/component so that it has direct access to all states and can also store references, etc.?
Thanks you,
Viktor
My question is just same as the title.
Let's say I wrote the following code.
class TODOList extends Component {
render() {
const {todos, onClick} = this.props;
return (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
onClick={onClick}
{...todo}
/>
)}
</ul>
);
}
}
const mapStateToProps = (state) => {
return {
todos: state.todos
}
}
const mapDispatchToProps = (dispatch) => {
return {
onClick(data){
dispatch(complete(data))
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(TODOList);
Now, after the last line, this code will export the TODOList component with the state as props. It's not that it contains state, but just received state and will have them as 'props', just like the method name 'mapStateToProps' explains.
In the medium post(https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) written by Dan Abramov, container component handles data as state, and presentational property do as props. Isn't it a presentational component that deals with data as props? I'm stuck with the idea that the right container should be one like below.
class CommentList extends React.Component {
this.state = { comments: [] };
componentDidMount() {
fetchSomeComments(comments =>
this.setState({ comments: comments }));
}
render() {
return (
<ul>
{this.state.comments.map(c => (
<li>{c.body}—{c.author}</li>
))}
</ul>
);
}
}
I'm not sure why react-redux named the API 'mapStateToProps', when I tried to make 'stateful'(not handling data by property) container component
First of all these guidelines are not part of the bible
you should write code that is easy to reason about for YOU and your TEAM.
I think you are missing something, A redux Container is different than a react Container.
I mean, connect will create the container for you, it doesn't mean the wraped component is a Container.
Basically you can export both versions from the same file, the Container (connected version) and the presentation version (the none connected one).
Another thing that usually throw people off, is the name of the function and argument of mapStateToProps.
I prefer the name mapStoreToProps as in
map the redux store to the component's props.
the name state can be confusing when we are in the context of react.
Edit
As a followup to your comment:
I totally didn't know these two are actually different. Could you please tell me about more details
They are different in the way that connect is creating a "Container" for you.
connect is a High Order Component that creates the Container Component for us with all the subscription logic + functions to pass portions of the store and action-creators to its children as props (mapStateToProps & mapDispatchToProps).
A "normal" Container is usually refers to a component that you write by hand, its often doesn't deal with how things should look but instead deal with certain logic of the app.
As for the other comments like
The connect HoC of react-redux just injects the properties you can request into your component. It returns a new component that is wrapped around your component so that it can update your component whenever the state you're interested in the redux store is modified
As i mentioned above, this is partially true. It's not just injecting the properties into our component, its subscribing to the store, grabbing it from the Provider (via context) and its doing all these with optimizations in mind, so we won't have to do it by ourselves.
I'm not sure how mapStateToProps can confuse someone. We are talking about a state management library
I've seen some devs that misunderstood this because react has a state and redux has a store (at least that's how it was called in most of the tutorials and documentations).
this can be confusing to some people that are new to either react or redux.
Edit 2
It was a bit confusing due to the sentence 'it doesn't mean the wraped component is a Container.' Why is the wrapped component not a container? Isn't a component created by connect also a container?
I mean that the wrapped component that you wrote doesn't have to be a Container.
You can connect a "Presentation" component:
const Link = ({ active, children, onClick }) => {
if (active) {
return <span>{children}</span>
}
return (
<a
href=""
onClick={e => {
e.preventDefault()
onClick()
}}
>
{children}
</a>
)
}
// ...
export default connect(mapState, mapDispatch)(Link)
mapStateToProps will be called when store data changes. It will pass the returned object as new props for the component. This will not affect the component's state. If you'd like to set a new state after the component got its new props you need to use another lifecycle method: static getDerivedStateFromProps (in earlier versions of react componentWillRecieveProps). The object returned by static getDerivedStateFromProps will be your new state.
https://reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class
connect() will connect your component to the redux store. Withouth the connect function (of course) your mapStateToProps will not work.
I'm not sure why react-redux named the API 'mapStateToProps'
We are talking about the store's state :)
The high level purpose is to seamlessly integrate Redux's state management into the React application. Redux revolves around the store where all the state exists. There is no way to directly modify the store except through reducers whom receive actions from action creators and for that to happen we need for an action to be dispatched from the action creator.
The connect() function directly connects our components to the Redux store by taking the state in the Redux store and mapping it into a prop.
This is power of Redux and its why we use it.
Lets say you are building a component called LaundryList and you want it to render a laundry list. After you have wired up the Provider in your "parent" component, I put it in quotes because technically Provider is a component so it becomes the parent.
You can then import the connect() function from react-redux, pass it mapStateToProps in order to get that laundry list from the Redux store into your LaundryList component.
Now that you have your list of linens inside of the LaundryList component you can start to focus on building a list of elements out of them like so:
class LaundryList extends Component {
render() {
console.log(this.props.linens);
return <div>LaundryList</div>;
}
}
That contains the list of linens object and for every list of linens inside of there we are going to return some jsx that is going to represent that linen on my list.
Back inside my laundry list component I will add a helper method inside the laundry list component called render list like so:
class LaundryList extends Component {
renderList() {
}
render() {
return <div>LaundryList</div>;
}
}
So this purpose of this helper method is to take the list of linens, map over them and return a big blob of jsx like so:
class LaundryList extends Component {
renderList() {
return this.props.linens.map((linen) => {
return (
);
});
}
render() {
return <div>LaundryList</div>;
}
}
I have a react app that ties into localStorage of the browser. On the startup of the app, the localStorage is populated with all the data that is needed to run the app. This data is pulled with AJAX from XML files and constructed to form a localStorageObject that the web app can use as its "database" of information to pull content from...
At the moment, The main component's state is set to the localstorage. So essentially I have the following:
constructor(props) {
super(props);
this.state = {
courseData : JSON.parse(localStorage.getItem("storageID"));,
}
}
The state contains an object that is the entirety of the localStorage. Now I have many children components, who also have children components themselves. Some are components that just need to render once, while others are going to need to rerender with interaction from the user.
After reading, it seems there are many ways to implement a solution. I could have all the components have state, but that's not needed. I could just have the main component have state, and no other component have state. And whenever the state of the main component changes, the props will be based down and reupdated.
Is there a specific method that is best?
This method works, but.
First of all, localStorage calls should be on a componentDidMount function. Otherwise, it wouldn't work on a server-side-rendering case.
Secondly, I'd implement all the initial data fetching on a parent function and then pass down data to the root of react tree:
const localStorageData = localStorage.getItem('some_data')
ReactDom.render(
document.getElementById('my-element'),
<MyComponent
localStorageData={localStorageData}
/>
)
if have many children components it will be difficult to manage state because of deep nesting.
I would recommend using Higher Order Component for your local storage implementation And Pass it down to children. Here How I would do it:
import React from 'react';
var HigherOrderComponent = (Component) =>
class extends React.Component {
state={locStorage:{}}
componentDidMount(){
this.setState({locStorage:window.localStorage.getItem("data")})
}
render() {
return (
<Component
locStorage={this.state.locStorage}
/>
)
}
};
export default HigherOrderComponent;
import HigherOrderComponent from './HigherOrderComponent'
const ChildComponent = ({locStorage}) => {
console.log(locStorage)
return (
<div>
</div>
);
};
export default HigherOrderComponent(ChildComponent);
I have a very simple React component, in the componentDidMount() method I fire a call to Firebases firestore to get a document. The first render call displays a template with the caption 'No Items' when the Firebase call has completed it re renders the component successfully with the items data.
However in the process it throws an error onto the console
index.js:2178 Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the HomePage component.
I have tried looking at other articles but as far I can see I am doing it the right way. Can anyone tell me what I am doing wrong?
import React from 'react';
import { firestore } from '../firebase/firebase'
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
items: ''
}
}
componentDidMount() {
firestore.collection('item').doc('P1zi3sqkgFuJ6Jw243SA').get().then( o => {
this.setState({items: o.data()});
});
}
render() {
const item = this.state.items
const template = item ? <h1>{item.title}</h1> : <h1>No item</h1>
return(
<div>
<h1>Home Page</h1>
{ template}
</div>
);
}
}
export default HomePage;
I managed to figure out why this error appears even though react is rendering the returned items from Firebase just fine.
When the page is refreshed on the component it throws the error, if I navigated to another component then to this one it would not appear. I also noticed that when the bundle is recompiled and that home component is refreshed it fires two request to Firebase.
Not sure what the underlying issue is but at least its a step in the right direction