How to update state before rendering in Reactjs with mobx - reactjs

This is my mobx store code.
First, 'projectGet()' must be executed to push the data from firestore.
#observable projectState = {
projects: []
};
projectGet = () => {
firebase
.firestore()
.collection("projects")
.get()
.then(snapshot => {
snapshot.forEach(doc => {
this.projectState.projects.push(doc.data());
});
})
.catch(err => {
console.log("Error getting documents", err);
});
};
After push the data into projectState, it should be read at the other .js file.
I ran the function inside of render.
But when I enter the homepage, it doesn't update state at first.
So, when I refresh the homepage, it updates the state.
However, I need to update the state at the first home page access.
I tried to use 'componentWilupdate', 'ComponentDidmount' etc.
It doesn't work at all.
Could you give me some recommendation for this problem?
render() {
const { Project } = this.props;
Project.projectGet();
return (
<div className="col s12 m6">
<ProjectList projects={Project.projectState.projects} />
</div>
);
}
I attached more code below.
import React from "react";
import ProjectSummary from "./ProjectSummary";
import { Link } from "react-router-dom";
const ProjectList = ({ projects }) => {
return (
<div className="project-list section">
{projects &&
projects.map(project => {
return (
<Link to={"/project/" + project.id} key={project.id}>
<ProjectSummary project={project} />
</Link>
);
})}
</div>
);
};
export default ProjectList;

You can use componentDidMount lifecycle method to make API calls before rendering, For example
componentDidMount() {
const { Project } = this.props;
Project.projectGet();
}
then in render
render() {
const { Project } = this.props;
return (
<div className="col s12 m6">
<ProjectList projects={Project.projectState.projects} />
</div>
);
}

Use componendWillMount to make API call before the component renders, if it is not updated then check whether the expected props are available with componentWillReceiveProps lifecycle method.
componentWillReceiveProps({Project}) {
Project.projectGet();
}
Once the props are changed you will get the change in the render

Related

how to show a new todo-item without refreshing the page?

I tried a lots of things , and this problem does not seem to go away , can someone help me with this ??
this is my app component :
function App() {
const [todo, setTodo] = useState([]);
async function getTodo() {
try {
const todo = await axios.get("http://localhost:5000/api/todos");
// console.log(todo.data)
setTodo(todo.data);
} catch (error) {
console.log("something is wrong");
}
}
useEffect(() => {
// Update the document title using the browser API
getTodo();
}, []);
return (
<div className="App">
<h1>My Todo List</h1>
<h2>My Todo List</h2>
<Task Todor={todo} />
<Write />
</div>
);
}
export default App;
and this is my todos component :
function Todos({ Todor }) {
return (
<div className="Todos">
{Todor.map(T => <Todo post={T} />)}
</div>
);
}
export default Todos;
and this is my todo component :
function Todo({ post }) {
return (
<div className="Todo">
<h2>{post.title}</h2>
</div>
);
}
export default Todo ;
and this my add component :
export default function Write() {
const [inputText, setInputText] = useState({
title: ""
});
function handleChange(e) {
setInputText({
...inputText,
[e.target.name]: e.target.value,
});
}
const [status, setStatus] = useState(false);
async function addItem(e) {
e.preventDefault();
const res = await axios.post("http://localhost:5000/api/todos", inputText);
setInputText(inputText)
console.log("response:", res)
setStatus(true);
setInputText("");
}
return (
<div className="container">
<div className="form">
<input onChange={handleChange} type="text" name="title" />
<button onClick={addItem}>
<span>Add</span>
</button>
</div>
</div>
);
}
the new items dont show until I refresh the page , how to do that without refreshing ?
because obviously that defeats the purpose of React !!
useEffect(() => {
// Update the document title using the browser API
getTodo();
}, []);
The code inside useEffect with empty dependencies array [] only runs on the first render, to run it on every render you should remove the empty array dependencies.
useEffect(() => {
// Update the document title using the browser API
getTodo();
});
Note: It is not a best practice because your component will invoke getTodo() every time rerendered. In your case, you can use a state variable to control where to re-run the getTodo funtion e.g:
const [isAddedSuccess, setIsAddedSuccess] = useState(false)
Everytime you add new item successfully, just setIsAddedSuccess(true) and your useEffect should look like below:
useEffect(() => {
// Update the document title using the browser API
if (isAddedSuccess) getTodo();
}, [isAddedSuccess]);

My search input and pagination aren't triggering anything in Reactjs

I'm fairly new to react.
My search input and pagination buttons aren't triggering anything and nothing comes up in the console, what is wrong with my code ?
I tried putting every functions in App.js to get it cleaner.
App.js
import React, { Component } from "react";
import List from './List';
let API = 'https://swapi.co/api/people/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
search: '',
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.bind(this);
this.updateSearch = this.updateSearch.bind(this);
}
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(API);
const json = await response.json();
this.setState({ results: json.results });
};
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
updateSearch(event) {
this.setState({
search: event.target.value.substr(0, 20)
});
}
render() {
return (
<div>
<List data={this.state} />
</div>
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
const { data } = this.props;
const { results, search, updateSearch, handleClick, currentPage, todosPerPage } = data;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
return item.name.toLowerCase().indexOf(search) !== -1;
});
const renderTodos = currentTodos.map((item, number) => {
return (
<Person item={item} key={number} />
);
});
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li className="page-link" key={number} id={number} onClick={handleClick} style={{cursor: "pointer"}}>{number}</li>
);
});
return (
<div className="flex-grow-1">
<h1>Personnages de Star Wars</h1>
<form className="mb-4">
<div className="form-group">
<label>Rechercher</label>
<input
className="form-control"
type="text"
placeholder="luke skywalker..."
value={search}
onChange={updateSearch}
/>
</div>
</form>
<div className="row mb-5">{renderTodos}</div>
<nav aria-label="Navigation">
<ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
</nav>
</div>
);
}
}
export default List;
The value of the input doesn't change one bit if I type in it and if I right click on a page number, the console gets me Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Element': '#4' is not a valid selector.
Any idea ?
The issue is that in the List class you attempt take updateSearch and handleClick out of data (which in turn comes from this.props). But updateSearch and handleClick are never placed inside data. If you log either of these methods to the console you'll see they are undefined.
To fix this, you need to pass updateSearch and handleClick from App to List. You can do this either by including the methods inside the data prop, or by passing them directly as their own props (which I would recommend).
For example, you can change the render method of App to look something like this:
render() {
return (
<div>
<List
data={this.state}
updateSearch={ this.updateSearch }
handleClick={ this.handleClick }
/>
</div>
);
}
Then in the render method of List you can do this:
const { data, updateSearch, handleClick } = this.props;
and remove the definitions of the two methods from the destructuring of data below.

React/Redux - Not dispatching action

I am new to react redux. I am trying to build a simple restaurant app with foursquare api. I have two actions which makes api calls to 1st. search the restaurant and 2nd one to fetch details like this.
Action.js
export const fetchVenueDetail = venueId => dispatch => {
console.log("dispatching");
dispatch({ type: FETCH_VENUE_REQUESTED });
fetch(
`https://api.foursquare.com/v2/venues/${venueId}?
client_id=${api_id}&client_secret=${api_key}&v=20180422`
)
.then(res => res.json())
.then(data => dispatch({ type: FETCH_VENUE, payload:
data.response.venue }))
.catch(err => console.log(err));
};
Here is my component
class VenueDetail extends Component {
componentDidMount() {
this.props.fetchVenueDetail(this.props.match.params.id);
}
render() {
console.log(this.props);
const { venue, isLoading } = this.props;
console.log("This text", venue);
return (
<React.Fragment>
<Header type="venueDetail" venueDetail={venue} />
<main className="main-content">
<section className="venue">
{ !isLoading ? <ImageList venueDetail={venue} /> : <Loader /> }
</section>
<div className="sidebar-wrapper">
<Sidebar type="tips" venueDetail={venue} />
</div>
</main>
</React.Fragment>
)
}
};
const mapStateToProps = state => ({
venue: state.venueReducer.venue,
isLoading: state.venueReducer.isLoading
})
export default connect(mapStateToProps, {fetchVenueDetail})(VenueDetail);
After testing I figured out it is not dispatching the action that's why I am receiving an empty response.
Here is the working sandbox code If anyone wants to check- https://codesandbox.io/s/7m4153p1jq
The actual problem, that you're getting error before your request starts in Sidebar component. There is no groups property in your venue object until your request is done.
If you add such check in your "tips" case it should run well:
const itemTips = venueTips.tips.groups ? venueTips.tips.groups[0].items : [];
You can add groups to your reducer and check for length property or choose another way, like wrapping all of the component, including Sidebar component, and display Loader instead of them. Something like this:
VenueDetail.js
{
(!isLoading && venue.id) ?
<React.Fragment>
<section className="venue">
<ImageList venueDetail={venue}/>
</section>
<div className="sidebar-wrapper">
<Sidebar type="tips" venueDetail={venue}/>
</div>
</React.Fragment> : <Loader/>
}
Here is updated codesandbox.
Ok So the problem was I was dispatching my action inside componentDidMount lifecycle but when i changed it back to componentWillMount. It works. Can somebody explain why?

Child component is rendering before data comes back from post request causes blank screen

After making post request the data post to the database and comes back in the response but props doesn't get passed down in time causing a undefined error in props resulting in blank screen. The ApiManager is a generic crud controller to handle request. I want to know should I use componentWillReceiveProps componentDidUpdate or shouldComponentUpdate and what are generally the best practices for child components updating appropriately
import React, { Component } from 'react'
import ZoneData from './ZoneData'
import { ApiManager } from '../utils'
class Zones extends Component {
constructor(){
super()
this.state = {
zone:{
name:'',
zipcode: ''
},
list: []
}
}
componentDidMount(){
ApiManager.get('http://localhost:3033/api/zone', null, (err, response) => {
if(err){
console.log(err)
return
}
console.log(response)
this.setState({
list: response.results
})
})
}
updateZone (event) {
event.preventDefault()
console.log('updateZone: '+event.target.id+'=='+event.target.value)
let updateZone = Object.assign({}, this.state.zone)
updateZone[event.target.id] = event.target.value
this.setState({
zone: updateZone
})
}
addZone () {
let updatedZone = Object.assign({}, this.state.zone)
ApiManager.post('http://localhost:3033/api/zone', updatedZone, (err, response) => {
if(err){
alert('ERROR '+err.message)
return
}
console.log("Post created: "+JSON.stringify(response))
let zoneList = Object.assign([], this.state.list)
zoneList.push(response.result)
this.setState({
list: zoneList
})
})
}
render(){
const listItems = this.state.list.map((zone, i) => {
return <li className="list-group-item" key={i}><ZoneData currentZone={zone}/></li>
})
return (
<div className="position-sticky">
<ul className="list-group">
{listItems}
</ul>
<input id='name' onChange={this.updateZone.bind(this)} className='form-control' placeholder="name"/>
<input id='zipcode' onChange={this.updateZone.bind(this)} className='form-control' placeholder="zipcode"/>
<button onClick={this.addZone.bind(this)} className='btn btn-primary'>Add Zone</button>
</div>
)
}
}
export default Zones
child component
import React, { Component } from 'react'
const ZoneData = (props) => {
return (
<div className="container">
<h2>{props.currentZone.name}</h2>
<span>{props.currentZone.zipcode}</span><br/>
<span>{props.currentZone.numPosts}</span>
</div>
)
}
export default ZoneData
child component props.currentZone.name comes back as undefined after making post request and the screen turns blank with out updating the child components props.
when I refresh the the new data that was posted to the database is there
I think, you should add some variable like isListReady to check if list is ready for showing and everything will be ok.
render(){
const {list} = this.state;
const isListReady = list && list.length;
const listItems = isListReady && list.map((zone, i) => {
return <li className="list-group-item" key={i}><ZoneData currentZone={zone}/></li>
})
return (
<div className="position-sticky">
<ul className="list-group">
{isListReady && listItems}
</ul>
<input id='name' onChange={this.updateZone.bind(this)} className='form-control' placeholder="name"/>
<input id='zipcode' onChange={this.updateZone.bind(this)} className='form-control' placeholder="zipcode"/>
<button onClick={this.addZone.bind(this)} className='btn btn-primary'>Add Zone</button>
</div>
)
}
You are getting the blank screen because you should use componentWillMount() instead of componentDidMount(). componentDidMount() makes an api call after the component has rendered. And I think you also need to do conditional rendering too, for listItems. Something like this:
render(){
const listItems = this.state.list.map((zone, i) => {
return <li className="list-group-item" key={i}><ZoneData currentZone={zone}/></li>
})
return (
<div className="position-sticky">
<ul className="list-group">
{this.state.isListAvailable && listItems}
</ul>
{// other code}
</div>
)
}
you can set isListAvailable to true when you have the list and the data.
Maybe its vacation time but it was a typo on response.result should have been response.results

State not updating in Component

Hey I am trying to create a simple to-do list and I have added the components necessary. However, the state is not being updated in the Title {this.state.data.length} and the TodoList {this.state.data}. A Codepen and the relevant code is below.
https://codepen.io/skasliwal12/pen/BREYXK
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input ref={node => {input = node;}} />
<button onClick={(e) => {
e.preventDefault();
addTodo(input.value);
input.value='';
}}> +
</button>
</div>
);
};
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo}</li>
});
return <div> {todoNodes} </div>;
}
const Title = ({todoCount}) => {
return (
<div>
<div>
<h1>To-do App {todoCount} items</h1>
</div>
</div>
);
}
class TestApp extends React.Component {
constructor(props) {
super(props);
this.state = { data : [] }
}
addTodo(val) {
let todo = {text: val}
this.state.data.push(todo);
this.setState = ({data: this.state.data});
console.log('state updated?')
}
render(){
return (
<div>
<Title todoCount={this.state.data.length}/>
<TodoForm addTodo={this.addTodo.bind(this)}/>
<TodoList todos={this.state.data}/>
</div>
);
}
}
ReactDOM.render(<TestApp />, document.getElementById('root'));
Quite simply it is important that you DO NOT MUTATE the state like you are doing here
this.state.data.push(todo);
It is hard to debug and adds side effects that are hard to keep track of. Following your approach you should copy the state to a var, update that var and then pass it as the new field in your state. Which could work but it's also something I do not recommend. A general good approach is to to compute the new state based on the old one
// this.state.data.push(todo); You can remove this line
this.setState(prevState => ({ data: prevState.data.concat(todo) }))
This will fix your issue and avoid mutating the state, which is something you should never do, only update the state using the setState method.
I also updated your TodoList which was not displaying properly, you have to access the text field of the todo in order to show something.
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo.text}</li>
});
return <div> {todoNodes} </div>;
}
https://codepen.io/anon/pen/MmRVmX?editors=1010

Resources