I have a react component with a form for updating database records.
Here's the thing: the data is loaded with React-Relay QueryRenderer component as follows:
class Update extends Component {
//constructor..
//some stuff
render() {
return(
<QueryRenderer
environment={environment}
query={UpdateQuery}
render={({error, props}) => {
//validations
return (
<Form loading={this.state.loading}>
//inputs
</Form>
)...
}/>
)}
The props variable is supposed to store the result from server response if successful. However, I need to call the update with this.state values.
I need a way to setState with props values.
I have tried with componentDidMount and using refs both string refs and callback ones to get defaultValue from Inputs. I got undefined values when calling this.refs
For now, it works if I call a function within QueryRenderer render function that sets the state with props if state is empty. E.g
function initValues(props){
if(!this.state.name)
this.setState({name: props.result.name})
}
But it results in an anti-pattern (pure render method) that I need to solve.
Edit:
For anyone wondering how I solved this. Thanks to charlie's answer I managed to create a UpdateForm component wrapper that receives the props from QueryRenderer, and in order to update my parent's component state I passed my handleChange function as props to my FormUpdate component
Use componentWillReceiveProps in your Form component
class Form extends React.Component {
...
componentWillReceiveProps(nextProps) {
if (nextProps.loading) return
this.setState({
name: nextProps.name
})
}
...
}
This will only set the state once as soon as the data is available, since QueryRenderer only calls render once after the data has loaded.
Related
I know we can easily send the content of mapStateToProps in the component's state by doing so :
constructor(props){
super(props);
this.state = {
filteredApps: this.props.apps
}
}
In this usecase, this.state.filteredApps gets filled with what was mapped to props from Redux.
But what if this.props.apps is only filled properly after an async call? In an async context, this.props.apps will probably be an empty array for when it is initialized until the real data is fetched. Take this as an example :
class AppFilterer extends React.Component {
constructor(props) {
super(props);
this.state = {
filteredApps : this.props.apps
}
}
componentWillMount() {
this.props.getApps();
}
render(){ return <div> </div> }
}
const mapStateToProps = state => {
let { apps } = state.Admin;
return { apps };
};
export default connect(mapStateToProps, { getApps })(AppFilterer);
In this case, my Redux action (which is caught by an Saga) this.props.getApps(); is the call that fills my props full of apps and is called from the componentWillMount function. It is initialized as an empty array and then gets filled with apps once the call is complete.
I wish to filter these apps once they are fetched from the API so want to put them inside my component's state so that I don't mess with the Redux state. What is the best practice for updating the component's state in this case? In other words, is there any way to take the result of a saga that has been mapped to props and set it into the component's state or am I looking for a weird pattern and should filter it some other way?
First of all API calls go in componentDidMount not in componentWillMount which is also now deprecated. Please refer this guide:
https://reactjs.org/docs/react-component.html
Secondly, when you are using redux state and mapping it to props, you should not set that in your component local state, that’s not a good practice. You’ll receive updated props when your promise will return and you can always rely on props in that scenario.
But if you still want to do that you can override componentDidUpdate(prevProps) which will be called when your props or state is updated. Here is where you can set your state if you still want to do that.
Note for your filter thing
You can do filtering in componentDidUpdate method like:
this.setState({filteredApps. this.props.apps.filter(<your filter logic>)})
I want to update my local state if the props were changed. But I could not find a suitable solution to this problem. getDerivedStateFromProps gives me only nextProps and prevState but I need both prevProps and nextProps. Please write any your solution that might solve this problem)
Situation: The Cart component (stateful) gets the fetched data from the props and puts that data in this.state.cart. The Cart component has counterHandler method that triggers whenever the counter is changed (onIcrease, onDecrease, onChange). This handler calculates the data and makes a new data based on the event ( calculating totalPrice and updating the amount of the item ). Then puts the new data into setState to update the UI. The state doesn't change because I do this:
static getDerivedStateFromProps(nextProps, prevState) {
if (!_.isEqual(nextProps.cart, prevState.cart)) {
return { cart: nextProps.cart };
} else return null;
}
I do this because I want to update the state when the real data is changed ( the data that comes from the props ).
Goal: updating the counter's and the total price's value without making any-side effect ( the values might be updated in the store or only for the local state ).
This is what I mean:
You might want to use componentDidUpdate which is called when the props on the component update. It receives prevProps and prevState as parameters, and you have access to "nextProps" as this.props.
For example
componentDidUpdate(prevProps, prevState) {
if (!_.isEqual(this.props.cart, prevState.cart)) {
this.setState ({ cart: this.props.cart });
}
}
You can use componentWillReceiveProps method of react class. This method will be executed when parent component will render, its child will execute componentWillReceiveProps method in child. In this method in child you can update state based on new props.
componentWillReceiveProps(nextProps){
//You can access current props via this.props
//You can access newProps from argument
//Change component state after comparing both props
}
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>;
}
}
Ok so this question is a bit tricky. I have been thinking about whether this is even correct concept wise, considering React is supposed to be a one-way flow of data, from parent to children, and not viceversa. But I would like to post the question anyway so I get different opinions and even possibly a way to get this to work.
In my app, I have a pretty large component that accepts forms as its children, and does some nifty React magic to pass its methods to the children so when the children elements are changed, they trigger the parent components methods that store the data in state and handles the form submissions. It works very nicely, however it is not so good at catching "defaultValues".
In a nutshell, I'm trying to trigger my parent method on the chilren's componentidMount() method, and it works, however, if there's more than one child trying to do this, the method gets called twice but it only uses the second child's dataset.
I have created a simplified version of my issue in the following code:
import React from 'react'
export class Parent extends React.Component {
constructor(props){
super(props)
this.state = {
data : {name:'james'}
}
this.updateData = this.updateData.bind(this)
}
updateData(key,data){
console.log('updating data')
this.setState({
data : {...this.state.data,[key]:data}
})
}
render(){
console.log(this.state)
return (
<div>
<Child1 updateData={this.updateData}/>
<Child2 updateData={this.updateData}/>
</div>
)
}
}
class Child1 extends React.Component {
componentDidMount(){
this.props.updateData('child1','myData')
}
render(){
return (
<div>
I am Child 1
</div>
)
}
}
class Child2 extends React.Component {
componentDidMount(){
this.props.updateData('child2','myData2')
}
render(){
return (
<div>
I am Child 2
</div>
)
}
}
This code will render 'updating data' twice on the console, but it will only update the state with the data sent in child2. Again, I can see how this may not be the best approach considering that im setting the state of a parent from its children, but it would be a good solution for setting default values on a parent component that gets reused a lot with different children.
Im all ears stack overflow
I think the problem is that setState does both updates at the same time (batches them) meaning the same initial state is used when merging both partial states. You need to use updater function as shown by kind user:
this.setState((prevState) => ({ data: { ...prevState.data, [key]: data } }));
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);