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?
Related
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]);
I am getting this problem TypeError: undefined is not a function and I did not recognize the error,
this is my code.
I have included the full code of this component in order to be clear
import React, {Component, useState,useEffect} from "react";
function Counter() {
const [searchTerm, setSearchTerm] = useState("");
const[materiel,setMateriels]=useState([]);
useEffect(() => {
fetch("http://localhost:5000/materiels")
.then((res) => res.json())
.then((data) => {
setMateriels(data);
console.log(materiels);
})
.catch(console.log);
}, []);
}
class searchMateriel extends Component {
render() {
return (
<div className="container">
<div className="col-xs-12">
<input type="text" placeholder="Search..." onChange={event => {
this.setState({searchTerm: event.target.value});
}}/>
{this.state.materiels
.filter((val) => val.nom.startsWith(this.statesearchTerm))
.map((val, key) => {
return (
<div className="user" key={{key}}>
<p>{val.nom}</p>
</div>
);
})}
</div>
</div>
);
}
state = {
materiels: [],
searchTerm: "",
}
componentDidMount() {
fetch('http://localhost:5000/materiels')
.then(res => res.json())
.then((data) => {
this.setState({materiels: data})
console.log(this.state.materiels)
})
.catch(console.log)
}
}
export default searchMateriel;
I have updated the code but still not working.
It is showing this error
Line 11:29: 'materiels' is not defined
The error is in the last line of my code, does anyone please have an idea?
Thanks in advance
Your code/use-case is wrong, in this code Counter is a functional component, so React except that to return some JSX's...
you get undefined because Counter returns nothing then you want to access the setSearchTerm method... basically, you will get the undefined is not a function react js error.
to store the input value in the state, you can pass the parent setState to the children or define a state in it.
NOTE: if you merely want to store your state in another place and process it, you can use hooks.
Counter in the code above is just a function, which actually doesn't return anything. Also Counter doesn't have setSearchTerm method which you are trying to access. You cannot use a hook outside of the top level of a functional component or hook see docs
As you are using class-based components, you should use setState method to update your state or switch to functional component and use useState hook
I don't see any usage of searchTerm in your code, but I am assuming, that you will add a function to your filter function.
class searchMateriel extends Component {
render() {
return (
<div className="container">
<div className="col-xs-12">
<input
type="text"
placeholder="Search..."
onChange={(event) => {
this.setState({ searchTerm: event.target.value });
}}
/>
{this.state.materiels
.filter((val) => val.nom.startsWith(this.statesearchTerm))
.map((val, key) => {
return (
<div className="user" key={{ key }}>
<p>{val.nom}</p>
</div>
);
})}
</div>
</div>
);
}
state = {
materiels: [],
searchTerm: "",
};
componentDidMount() {
fetch("http://localhost:5000/materiels")
.then((res) => res.json())
.then((data) => {
this.setState({ materiels: data });
console.log(this.state.materiels);
})
.catch(console.log);
}
}
export default searchMateriel;
Also, I wanted to mention that in class-based components state is merged when you are using setState but when you are using useState hook state is completely replaced
If you would like to replace your class-based component with functional one, it could look like this:
const searchMateriel = () => {
const [searchTerm, setSearchTerm] = useState("");
const [materiels, setMateriels] = useState([]);
useEffect(() => {
fetch("http://localhost:5000/materiels")
.then((res) => res.json())
.then((data) => {
setMateriels(data);
console.log(materiels);
})
.catch(console.log);
}, []);
return (
<div className="container">
<div className="col-xs-12">
<input
type="text"
placeholder="Search..."
onChange={(event) => {
setSearchTerm(event.target.value);
}}
/>
{materiels
.filter((val) => val.nom.startsWith(searchTerm))
.map((val, key) => {
return (
<div className="user" key={{ key }}>
<p>{val.nom}</p>
</div>
);
})}
</div>
</div>
);
};
export default searchMateriel;
More about useEffect you can read here
seeing you are not defining state in constructor. this may be the reason. Constructor loads before component is mounted. https://reactjs.org/docs/react-component.html#constructor
i am trying to make a CRUD app in DRF-Reactjs by following Tania rascia's example
i have successfully implemented add, delete, list view. but i am trying to edit a specific row which is not updating in DRF backend. but the edited row is shown in the frontend list. why it is not updating in django admin list?
in DRF side views.py:
#api_view(['POST'])
def TodoUpdate(request, pk):
todo = Todo.objects.get(id=pk)
serializer = TodoSerializer(instance=todo, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
i am using cors header to interface between frontend to backend. here is the frontend code for edit:
App.js:
import React,{Fragment, useState,useEffect} from 'react'
import EditList from './components/EditList';
import axios from 'axios'
export default function App() {
const initialTodoSate = { id: null, title: "", body: "" };
const [todos, setTodos] = useState([]);
const [todoList, setTodolist] = useState(initialTodoSate);
const [editing, setEditing] = useState(false);
useEffect(()=>{
axios.get("http://localhost:8000/api/todo-list",{})
.then(res=>{
setTodos(res.data)
}).catch(err=>{
console.log(err)
})
},[])
const addTodoList = (todo) => {
axios
.post("http://localhost:8000/api/todo-create/",todo)
.then((res) => {
console.log(res.data);
todo.id = todos.length + 1;
setTodos([todo, ...todos]);
})
.catch((err) => {
console.log(err);
});
};
const deleteTodo = (id) => {
setEditing(false);
axios.delete(`http://localhost:8000/api/todo-delete/${id}/`)
.then(res=>{
setTodos(todos.filter((todo) => todo.id !== id));
}).catch(err=>{
console.log(err)
})
};
const updateTodo = ( id,updatedTodo) => {
axios
.post(`http://localhost:8000/api/todo-update/${id}/`, id)
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err);
});
setEditing(false);
setTodos(todos.map((todo) => (todo.id === id ? updatedTodo : todo)));
};
const editRow = (todo) => {
setEditing(true);
setTodolist({
id: todo.id,
title: todo.title,
description: todo.description,
});
};
return (
<div className="container">
<h1>Django-based Todo with React Hooks</h1>
{editing ? (
<Fragment>
<h3>Edit Task</h3>
<EditList
editing={editing}
setEditing={setEditing}
todoList={todoList}
updateTodo={updateTodo}
/>
</Fragment>
) : (
<Fragment>
<CreateTodo addTodoList={addTodoList} />
<hr />
</Fragment>
)}
<div className="flex-row">
<div className="flex-large">
<TodoList todos={todos} editRow={editRow} deleteTodo={deleteTodo} />
</div>
</div>
</div>
);
}
and EditList.js:
import React, { useState,useEffect } from "react";
export default function EditList({ todoList, setEditing, updateTodo }) {
const [todo, setTodo] = useState([todoList]);
useEffect(() => {
setTodo(todoList);
}, [todoList]);
const handleChange = (e) => {
const { name, value } = e.target;
setTodo({ ...todo, [name]: value });
};
return (
<form
onSubmit={(e) => {
e.preventDefault();
updateTodo(todo.id, todo);
}}
>
<label>Title:</label>
<br />
<input
type="text"
name="title"
value={todo.title}
onChange={handleChange}
/>
<br />
<label>Description:</label>
<br />
<input
type="text"
name="body"
value={todo.body}
onChange={handleChange}
/>
<br />
<button>Update Task</button>
<button onClick={() => setEditing(false)} className="button muted-button">
Cancel
</button>
</form>
);
}
when i try to edit one row with title and body, it is edited and after pressing the update button, the updated row included in the list. but the problem is when i look into the django admin it has not been updated and when i check the development tools, i found an error:
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
at input
at form
at EditList (http://localhost:3000/static/js/main.chunk.js:511:3)
at div
at App (http://localhost:3000/static/js/main.chunk.js:70:83)
console. # vendors~main.chunk.js:31671
where am i having the mistake?
can anyone help me please? please let me know if you need any additional codes or information.
Trying to update something should be done in a put request, not a post request. This is a REST API convention, but a discrepancy may have some consequence down the line.
In this case, the error in your development tools is telling you that one of your components has an onChange/onSubmit etc property that is changing over the course of one mount from null to a function. This is not what's causing your issue, but I suspect it can be fixed by declaring the code in a handleSubmit function and then putting that into your onSubmit.
I think the error that's actually causing your problem is that the updatedTodo is not being sent to the backend. All that is being sent is the id (second parameter of axios.post). So if you pause the backend during execution, you would see that request.data = the id only, when it should be TodoSerializer's readable fields.
PS:
You can add a "debugger;" statement in the code after the updateToDo async request error to see what the error actually is (read more on the development tools debugging - browser dependent).
Don't abuse fragments - in this case, it would make for a more accessibility-friendly experience if you use divs in most of these components. Wouldn't it make more sense if the heading of some content was grouped with the content? https://developers.google.com/web/fundamentals/accessibility/focus/dom-order-matters
I wonder why my component SearchResults is rendered twice.
In MainPage component I want to pass offers to child component SearchResults:
const mainPage = () => {
const [offers, setOffers] = useState(null);
useEffect(() => {
onInitOffers();
}, [])
const onInitOffers = () => {
axios.get('/offers')
.then(response => {
setOffers(response.data);
})
.catch(error => {
console.log(error);
})
}
const searchResults = (
<SearchResults
searchedOffers={offers}
/>
);
return (
<Aux>
<div className={classes.container}>
<div className={classes.contentSection}>
{searchResults}
</div>
</div>
</Aux>
)
}
export default mainPage;
Why the component SearchResults is rendered twice? How to correctly pass offers to child component using hooks?
In my child component SearchResults I have to add if condition to avoid error map is not a function:
const searchResults = props => {
useEffect(() => {
console.log("RENDER");
console.log(props.searchedOffers) --> null for the first time
}, [props.searchedOffers]);
let offers = null;
if (props.searchedOffers !== null) { --> props.searchedOffers is not null after the second render
offers = props.searchedOffers.map(offer => {
return (
<Grid key={offer.id}>
<SearchResult key={offer.id} offer={offer}/>
</Grid>
)
});
}
It's rendered twice because, when the element mounts, you set offers to null. If you want to make sure you only render the SearchResults component when offers isn't null, you can do something like:
return (
<Aux>
<div className={classes.container}>
<div className={classes.contentSection}>
{offers && <SearchResult searchedOffers={offers} />}
</div>
</div>
</Aux>
)
If you want to be super sure offers is an array, you can do something like {Array.isArray(offers) && <SearchResult searchedOffers={offers} />}.
Often when doing something async like this, you might elect to actually use a ternary operator to show a loading indicator while the fetch is happening:
return (
<Aux>
<div className={classes.container}>
<div className={classes.contentSection}>
{offers ? <SearchResult searchedOffers={offers} /> : "Loading..."}
</div>
</div>
</Aux>
)
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