I am using react-redux code structure and this is my first try with react-redux.I have cloned a github repository from Here and started editing it.
My directory structure:
Here schema is parent component and header and tables are 2 child components.Tables is showing data from localstorage through redux store.
Initializing store:
const initialState = JSON.parse(window.localStorage.getItem('test'));
const store = createStore(Reducers, initialState, compose(applyMiddleware(...middleware), extension));
Now an event is triggered from Header and sent to schema and in the response of this event schema is updating localstorage by requesting to server and saving server's response in localstorage as follows:
Schema.js:
class Schema extends PureComponent {
constructor(props) {
super(props);
this.table = '';
getTables();
}
myCallback = () => {//triggered by child(Header)
getTables();
}
getTables = () => {
axios.get(url)
.then((response) => {
if(response.data.status==0){
window.localStorage.setItem('test',JSON.stringify(response.data));
this.tables=JSON.parse(window.localStorage.getItem('test'))
});
}
render() {
console.log(this.tables);//this is giving updated value at each updation
return (
<div className='container-fluid'>
<Header callbackFromParent={ this.myCallback } />
<br />
<br />
<Tables val={ this.tables } />
</div>
);
}
}
Here is code for Tables.js:
class Tables extends Component {
props: Props
render() {
let {tables,val } = this.props;
console.log(JSON.parse(window.localStorage.getItem('test')));//this is giving updated value at each updation in localstorage
console.log(val);//this is also giving updated value at each updation in localstorage
tables=val;
console.log(tables);this is also updating in realtime.
return (
<div className='table-wrapper'>
{ tables.map((table) => (
<Table
key={ table.id }
data={ table }
/>
))}
</div>
);
}
}
type Props = {
tables: Array<TableType>
};
The issue is whenever header triggers callback, schema updates value of localstorage, this updation also re render Tables component. Also an updated value can be seen in render of Tables component but the tables which are shown are from previous saved value. To get current value in tables, we need to refresh the page.
is it a mistake in code flow or i need something else for this?
The idea is that react will trigger rendering of component whenever the component state or props is updated.
If the component props are updated in parent component you will still need to update the component state to make the next render in inner component
The key of this is using componentWillReceiveProps
I updated your code with the code below:
Basically i did the following:
1- I used component state for Schema, Tables, and Table
2- I used this.setState whenever i need to make updates to state (this is very important)
3- I make sure that when a component props is updated in parent i update the component state as well using componentWillReceiveProps and this will make the new render with updated data
Schema component:
class Schema extends Component {
constructor(props) {
super(props);
this.state = { tables : { } }
this.getTables = this.getTables.bind(this);
this.myCallback = this.myCallback.bind(this);
}
componentWillMount(){
this.getTables();
}
myCallback = () => {
//triggered by child(Header)
this.getTables();
}
getTables = () => {
axios.get(url)
.then((response) => {
if(response.data.status==0)
{
window.localStorage.setItem('test',JSON.stringify(response.data));
this.setState({
tables : JSON.parse(window.localStorage.getItem('test'))
});
}
);
}
render() {
//this is giving updated value at each updation
console.log(this.state.tables);
return (
<div className='container-fluid'>
<Header callbackFromParent={ this.myCallback } />
<br />
<br />
<Tables tables={this.state.tables} />
</div>
);
}
}
Tables Component
class Tables extends Component {
constructor(props) {
super(props);
this.state = { tables : { } }
}
componentWillMount(){
this.setState({
tables : this.props.tables
})
}
componentWillReceiveProps(nextProps){
this.setState({
tables : nextProps.tables
})
}
render() {
console.log(JSON.parse(window.localStorage.getItem('test')));//this is giving updated value at each updation in localstorage
console.log(this.state.tables);//this is also giving updated value at each updation in localstorage
return (
<div className='table-wrapper'>
{ this.state.tables.map((table) => (
<Table key={ table.id } data={ table } />
))
}
</div>
);
}
}
And finally a dummy Table component to show that you will also need to handle props update using componentWillReceiveProps to make sure each individual table component did render after props update
And probably this is where you have the issue ... because the tables are showing old data but the console.log of Tables component is logging new data which means Each individual Table component is not rending after the update
class Table extends Component {
constructor(props) {
super(props);
this.state = { data : { } }
}
componentWillMount(){
this.setState({
data : this.props.data
})
}
componentWillReceiveProps(nextProps){
this.setState({
data : nextProps.data
})
}
render() {
console.log(this.state.data);
return (
<table className='table'>
{this.state.data}
//use your table data here
</table>
);
}
}
Important Edit:
As mentioned by react documentation componentWillReceiveProps might get called even if the props have not changed, thats why in some situation you might need to consider comparing this.props with nextProps to make sure that you really got new updated props and based on that you update the component state ....
Related
I have a parent component housing two children components(AddPersonForm and PeopleList). When I submit a name via the AddPersonForm, I expect it to be rendered in the PeopleList component, but it doesn't.
Here is my AddPersonForm:
class AddPersonForm extends React.Component {
state = {
person: ""
}
handleChange = (e) => this.setState({person: e.target.value});
handleSubmit = (e) => {
if(this.state.person != '') {
this.props.parentMethod(this.state.person);
this.setState({person: ""});
}
e.preventDefault();
}
render() {
return (
<form onSubmit={this. handleSubmit}>
<input type="text" placeholder="Add new contact" onChange={this.handleChange} value={this.state.person} />
<button type="submit">Add</button>
</form>
);
}
My PeopleList component:
class PeopleList extends React.Component {
constructor(props) {
super(props);
const arr = this.props.data;
this.state = {
listItems: arr.map((val, index) => <li key={index}>{val}</li> );
}
}
render() {
return <ul>{this.state.listItems}</ul>;
}
}
Now the parent component, ContactManager:
class ContactManager extends React.Component {
state = {
contacts: this.props.data
}
addPerson = (name) => {
this.setState({contacts: [... this.state.contacts, name]});
render() {
return (
<div>
<AddPersonForm parentMethod={this. addPerson}×/>
<PeopleList data={this.state.contacts} />
</div>
);
Please what I'm I doing wrong, or not doing?
The issue is in your PeopleList component. The state object which renders your list is created in the constructor when the component mounts, but you have no way of updating it when it recieves new values. It will always give you the initial value.
You could introduce a lifecycle method, componentDidUpdate, which would allow you to compare the previous props to the new props when they arrive, and update the state accordingly. I would recommend you not do this for two reasons:
Storing props directly in a components state is not good practice. You are just creating a copy of the state in the component above and that creates opportunities for confusion and stale values when one of them updates. Ideally, each piece of data should live in only one place.
If all PeopleList is doing is rendering your data, then it doesn't need any state at all. It can act as a display component that maps your props in place and doesn't have to worry about updating itself or managing its own data. This would actually make it a good candidate for conversion into a functional component.
class PeopleList extends React.Component {
render() {
return (
<ul>
{this.props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}
}
You are initializing PeopleList with props when its created and mounted but then you are not using new values of props for updating it.
To fix your issue use current value of prop when rendering:
class PeopleList extends React.Component {
render() {
return <ul>{ this.props.data.map((val, index) => <li key={index}>{val}</li>) }</ul>;
}
}
I have an app built with ReactJS. Its purpose is to display recipes, searched in food2fork API.
I have no problems with updating state of parent component. Data is fetched after clicking 'search' button in app.
My issue is related with sending fetched data as props to child component and properly displaying received recipes based on current search.
handleChange is only for handling input.
handleSearch is what I wanted to use 'onClick' of a button to display data fetched from API.
Fetched recipes should be displayed in Results component.
Hope it is clear :)
Besides only passing state as props from Parent component and using it in Child component, I also tried to update Child state based on received props with lifecycle methods - maybe I haven't used them corrently ...
Parent component:
import React, { Component } from 'react';
import Results from './Results';
class Recipes extends Component {
constructor(props){
super(props);
this.state = {
search: '',
recipes: []
}
}
handleChange=e=>{
this.setState({
search: e.target.value
})
}
handleSearch =()=>{
if(this.state.search !== ''){
const url = `https://www.food2fork.com/api/search?key=367d2d744696f9edff53ec5b33a1ce64&q=${this.state.search}`
fetch(url)
.then(data => data.json())
.then(jsonData => {
this.setState((jsonData)=> {return {
recipes: jsonData}
})
})
} else {
console.log('empty')
}
}
render() {
return (
<Wrapper>
<SearchBar
value={this.state.search}
type='search'
onChange={this.handleChange}>
</SearchBar>
<SearchButton onClick={this.handleSearch}>SEARCH</SearchButton>
<Results recipes={this.state.search}/>
</Wrapper>
);
}
}
export default Recipes;
CHILD COMPONENT 'Results' which should receive updated recipe list as props and display these recipes.
import React from 'react';
import Recipe from './Recipe';
class Results extends React.Component {
render(){
return (
<Container>
<RecipesList>
{this.props.recipes.map(item =>
<Recipe
f2fURL={item.f2f_url}
image={item.image_url}
publisher={item.publisher}
publisherURL={item.publisher_url}
recipeID={item.recipe_id}
source={item.source_url}
title={item.title}
/>)}
</RecipesList>
</Container>
);
}
};
As #yourfavoritedev mentioned, you have a typo on Results props. It should be
recipes={this.state.recipes} instead of recipes={this.state.search}
You should also change:
this.setState((jsonData)=> {return {
recipes: jsonData}
})
for:
this.setState({ recipes: jsonData })
The updater function will be something like this (documentation here):
(state, props) => stateChange
So the jsonData you are using on your setState is actually the previous state and not the data coming from the api call.
Your problem is here
this.setState((jsonData)=> {return {
recipes: jsonData}
})
inside your ajax response.
Change this to
this.setState({recipes: jsonData});
This should set the recipes object correctly.
I am passing a list from the parent component's state to a child component's <CheckboxList /> props. The list should then show in the child component. After fetching the list elements from the database I use setState to update the list but the child component is not re-rendered with the new list.
class ParentComponent extends Component {
state = {
users: [{ username: 'before' }]
}
componentDidMount() {
const result = [];
db.collection("users").get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
let user = doc.data();
result.push(user);
});
}).then(() => {
this.setState({
users: result
})
})
}
render() {
return (
<div>
<h1>List of users</h1>
<CheckboxList list={this.state.users} />
</div>
)
}
}
The list shows before and it does not update with content from the database. I checked and the values are fetched from the database, they are just not passed to the <CheckboxList /> after the setState. Can anybody help me?
The problem was that I was retrieving the list from the CheckboxList status and then using it through that. Now I have fixed it. I am retrieving it in the CheckboxList render(), storing it into a variable and then using it from there.
I'm running into a problem getting a child react component to update when its parent stage changes. I have an Editor parent component that sets its state and then updates the state if the component receives an updated schedule (from a graphQL mutation component).
The problem is that componentDidUpdate triggers which does trigger the Modefield to update, but it is before the setState in componentDidUpdate can update the state. This means the child doesn't update. (Note- I know a more idiomatic way is to get rid of state all together, but this way allows a field to both edit and create a new one.)
How can I cause the child to update based on the parent's state change?
export const updateScheduleMutation = gql`
mutation updateScheduleMutation(
$id: ID!
$mode: String
) {
updateSchedule(
id: $id
mode: $mode
) {
id
mode
}
}
`;
class EditorWrapper extends React.Component {
constructor(props) {
super(props);
this.state = { scheduleId: props.scheduleId || '' };
}
render() {
return (
<Mutation mutation={updateScheduleMutation}>
{(updateSchedule, { mutationData }) => <Editor {...data} updateSchedule={updateSchedule} />}
</Mutation>
)
}
}
class Editor extends React.Component {
constructor(props) {
super(props);
const { schedule } = props;
if(schedule === null){
this.state = {
schedule: { mode: schedule.mode || "" }
};
}
}
componentDidUpdate(prevProps) {
if (prevProps.schedule !== this.props.schedule) {
this.setState({ ...this.props.schedule });
}
}
changeInput = (path, input) => {
const { updateSchedule, schedule } = this.props;
const field = path.split('.')[1];
updateSchedule({ variables: { id: schedule.id, [field]: input } });
this.setState({ [path]: input });
};
render() {
return (
<ModeField input={this.state.schedule.input} />
);
}
}
const ModeField = ({input}) => FormControl value={input} />
EDIT: I updated the component to show the higher level graphQL wrapper. The reason why I wanted state in the Editor component is that in the event the graphQL query comes back as null, I set this.state.mode to an empty string, which I then update on change. Then, I would create a new schedule item on submit.
LIFT THE STATE UP! Try to manage the base state of your data in parent component and use the data as props in your component:
You also can try getDerivedStateFromProps, but before check the react blog advices:
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
I want to fetch data from server periodically and refresh rows when data is fetched by setState() but the rows doesn't re-render after setState().
constructor(props) {
super(props);
this.state = {
rows: []
}
this.refreshList = this.refreshList.bind(this);
}
refreshList() {
req.get('/data').end(function (error, res) {
// type of res is array of objects
this.setState({
rows: res
});
});
}
// call this method on button clicked
handleClick() {
this.refreshList();
}
render() {
return(
<div>
<button onClick={this.handleClick}>Refresh List</button>
<Table rows={this.state.rows}/>
</div>
);
}
when call refreshList() new feteched data doesn't render.
My table component is:
// Table component
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Thanks a lot for your help. How can I refresh list on click button?
Your table component never changes its state after the construction. You can fix it easily by updating the state from new props:
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
componentWillReceiveProps(newProps) {
this.setState({
rows: newProps.rows
});
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
However, if your table component is so simple, you can make it stateless and use props directly without setState():
export class Table extends Component {
render() {
return (
<div>
{this.props.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Note there is no need for constructor now. We could actually make it a functional component.
Use an arrow function:
req.get('/data').end((error, res)=> {
// type of res is array of objects
this.setState({
rows: res
});
});
With the ES5 style callback function, the context of this gets lost.
You could also bind this directly to a local variable, i.e., var that = this and stick with the function syntax, but I think most would agree what the ES6 arrow syntax is nicer.