I am learning react-redux, I am creating a simple CRUD app using JSON placeholder, now I am able to display data and delete data using post and delete method's but I can't figure out how to update data with put method in redux, I need help.
**
Here is a live demo in the sandbox: redux live demo
**
Here is what I have so far, user component (just part of codes)
return(
<div>
<table id="users">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
{userData &&
userData.users &&
userData.users.map(user =>
<tbody>
{user.editing ? <UserForm user={user} key={user.id} />:
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>
<button key={user.id} type="button" className="btn btn-danger btn-link" onClick={() => deleteUser(user.id)}>
<i className="material-icons">delete</i>
</button>
<button key={user.id} type="button" className="btn btn-success btn-link" onClick={() =>editUser(user.id)}>
<i className="material-icons">edit</i>
</button>
</td>
</tr>
}
</tbody>
)}
</table>
</div>
)
And here is userfom component
import React from 'react'
import { useForm } from "react-hook-form";
function UserForm() {
const { edit, handleSubmit} = useForm();
return (
<div>
<form onSubmit={handleSubmit}>
<input name="name" defaultValue="test" ref={edit} />
<input type="submit" />
</form>
</div>
)
}
export default UserForm
And here is Edit user in reducer
case ActionTypes.EDIT_USER:
return{
...state,
users:state.users.map((user)=>user.id === action.payload ? {
...user,editing:!user.editing
}:user)
}
Now when I click edit and submit the data, it refreshes the page and nothing is updated in user info (check it here live
What is wrong with my code?
Well I don't have a lot of knowledge about react-hook-form but I'll try to help you, first you need to pass a function to your 'handleSubmit' because otherwise I think that you don't prevent the default behaviour of a submit, I mean the handleSubmit function doesn't do a 'event.preventDefault()', so you can put the following below your useForm hook:
const onSubmit = data => {
console.log(data);
}
Then in your jsx you will have
<form onSubmit={handleSubmit(onSubmit)}>
I'm not sure because as I told you I don't use react-hook-form, but I think another bug that I saw is that you are trying to get a 'edit' property from the useForm hook, well that won't work, you are not declaring a variable there, you are trying to access a property from the useForm hook, so in order of tracking the changes of your inputs you should use 'register', I mean you should have the following code:
const { register, handleSubmit } = useForm();
And you should update your jsx with the following:
<input name="name" defaultValue="test" ref={register} />
Now with every submit you will have your form with the changes in the console.log that we add in our onSubmit function.
You already connected redux with your Users component, I mean you are using mapDispatchToProps and because of that inside your Users components you will be able to access the edit prop to dispatch an editUser action. So in order to continue your work with redux you can pass that prop via this.props.editUser to your UserForm component and continue. Another option is connect the UserForm component with redux and access the editUser prop.
I've modified your code, update user can work now.
revised version demo
redux/user/Users
// need to pass whole user data as parameter
export const updateUser = data => {
return dispatch => {
axios
// add data into axios second arg
.put(`https://jsonplaceholder.typicode.com/users/${data.id}`, data)
.then(response => {
dispatch(editUser(data.id));
// refresh user list after successfully update
dispatch(fetchUsers());
})
.catch(error => {
const errorMsg = error.message;
dispatch(fetchUsersFailure(errorMsg));
});
};
};
components/UserForm:
You don't need react-hook-form, just use the useDispatch hook that provided by react-redux.
import React from "react";
import { useDispatch } from "react-redux";
import { editUser, updateUser } from "../redux/acitons/users/Users";
function UserForm({ user }) {
const dispatch = useDispatch();
const [name, setName] = React.useState(user.name);
const handleSubmit = () => {
dispatch(updateUser({ ...user, name }));
};
const handleCancel = () => {
dispatch(editUser(user.id));
};
return (
{/* I modify it to inline edit row */}
<tr>
<td>{user.id}</td>
<td>
<input
defaultValue={user.name}
onChange={e => setName(e.target.value)}
/>
</td>
<td>
<button type="button" className="btn" onClick={handleCancel}>
<i className="material-icons">Cancel</i>
</button>
<button
type="button"
className="btn btn-success btn-link"
onClick={handleSubmit}
>
<i className="material-icons">save</i>
</button>
</td>
</tr>
);
}
export default UserForm;
But you will notice that new user list is still old after update successfully, this is normal
https://github.com/typicode/jsonplaceholder/issues/42#issuecomment-284507310
and I suggest you can group your actions/reducer/constants into one file by feature, this is useful when your app grow, You can easily find the file you want to modify when your code base becomes very large.
for example:
|-- reducers
|---- auth.js
|---- counter.js
|---- index.js
|---- store.js
|---- users.js
This approach actually has a name called duck pattern
Related
If the terms used in question are incorrect please bare with me as this is the first time i'm using react js. I have written some code by referring lot of blogs, youtube, docs etc. and now i'm stuck since it is a mix of everything.
I have a requirement where i make a get call to an endpoint (about.js component) which returns json data which will be passed to (workspace.js component) where it is rendered and displayed. so far it is working fine.
next step, there is a link 'delete'(in cards element of workspace.js) on clicking, it should make a post call to an endpoint with the project_name. here i'm unable to make it work (confused with const, functions etc).
below is the code : (about.js)
import React, { useState, useEffect } from "react";
import Card from "react-bootstrap/Card";
import "./About.css";
import axios from "axios";
import Account from "./Workspace";
function About() {
const [resp_data, setdata] = useState("");
useEffect(() => {
const axios = require("axios");
axios({
method: "get",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
url: "http://127.0.0.1:8000/api/projects/",
})
.then(function (response) {
setdata(response);
})
.catch(function (error) {
console.log(error);
})
.then(function () {});
}, []);
if (resp_data != "") {
return (
<div>
<Account user={resp_data} />
</div>
);
} else {
return <h2>Loading...</h2>;
}
}
export default About;
workspace.js
import React, { Component } from "react";
import Card from "react-bootstrap/Card";
const Account = (props) => {
function handleClick(event) {
alert(event);
//need to get the project_name here
//make post call to an endpoint with project_name as data
}
const users = props.user.data;
return (
<div>
{users.map((user) => (
<div className="card-rows">
<Card className="card" key={user.Id}>
<Card.Body>
<Card.Title>
<b>Project : </b>
{user.project_name}
</Card.Title>
<Card.Subtitle className="mb-2 text-muted">
<b>DataSet : </b>
{user.dataset_name}
</Card.Subtitle>
<Card.Subtitle className="mb-2 text-muted">
<b>DataType : </b>
{user.data_type}
</Card.Subtitle>
<Card.Link
href="#"
name="hello"
className="delete"
onClick={this.handleClick({user.project_name})} // call handleclick and the projectname should be available within the function
>
Delete
</Card.Link>
<Card.Link href="/launch" className="launch">
Launch
</Card.Link>
</Card.Body>
</Card>
</div>
))}
</div>
);
};
export default Account;
it would be a great assistance if anyone could help
If you are in an functional component, you can get the user prop by adding curl braces. Also modify your onClick.
React onClick Event Handling
I would recommend you to go through React Documentation thoroughly if starting out.
This should work for you.
const Account = ({user}) => {
//use curly braces around props to fetch user prop
function handleClick(project_name) {
alert(project_name);
//need to get the project_name here
//make post call to an endpoint with project_name as data
}
return(
... //above code
<Card.Link
href="#"
name="hello"
className="delete"
onClick={() => handleClick(user.project_name)}
>
Delete
</Card.Link>
... //below
)
I believe the issue is how you are handing the onClick function in the Card.Link component.
OnClick functions take a function to handle the event. So right now you are just invoking a function but that wouldnt have any effect on the event that is implicitly being passed into the component.
More can be found here: https://reactjs.org/docs/handling-events.html
<Card.Link
href="#"
name="hello"
className="delete"
onClick={() => this.handleClick({user.project_name})} />
The problem in your implementation lies here
onClick={this.handleClick({user.project_name})}
because this would make the call while component gets rendered. But the react synthetic event handlers expect a function reference to be passed.
In order to achieve the same You can simply convert the onClick handler for Delete button
from
onClick={this.handleClick({user.project_name})}
to
onClick={() => this.handleClick(user.project_name)}
That would look like below
<Card.Link
href="#"
name="hello"
className="delete"
onClick={() =>
this.handleClick(user.project_name)}>
Delete
</Card.Link>
So, directly the handleClick method would receive the project_name.
const handleClick = project_name => {
console.log(projectName);
//You can use `project_name ` for making the API call.
}
So guys, I've been working on my Landing and Dashboard page.
So workflow of the page is this:
User gets on Landing page where he can choose to insert into form location, or press a button to recive all locations. Now on the backend I've made two APIs one to get all locations, and second where I've added :location as req.body.param and then filter locations based on that param. And everything works fine in postman.
Now because I've got two ways of user getting locations(all of them or some that he wants) I've thinked that I place two useEffects inside if statement like this:
const filter = props.location.data;
if (filter) {
useEffect(() => {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter);
setContent(res.data);
};
fetchFiltered();
}, []);
} else {
useEffect(() => {
const fetchPosts = async () => {
const res = await ArticleService.articles();
setContent(res.data);
};
fetchPosts();
}, []);
}
So my logic behind this was if there is filter inside props.location execute me useEffect which gets data from ArticleService who then send filter inside of a api url. If there is no filter just retrieve me all data, and setContent to res.data.
But when I compiled the code error is this: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render
Is there some way of doing this with my logic or I need to create two components: one normal dashboard and second for filtered result?
Landing.js where user sends location
<Form>
<div className='form-group'>
<Input
type='text'
className='form-control text-center'
name='name'
placeholder='Enter desired location'
value={location}
onChange={onChangeLocation}
/>
<Link to={{ pathname: '/dashboard', data: location }}>
<i className='fas fa-check'></i>
</Link>
</div>
<p className='text-center'>or</p>
<Link className='btn btn-primary btn-block' to='/dashboard'>
Show all locations
</Link>
</Form>
Dashboard.js
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import Pagination from 'react-js-pagination';
import ArticleService from '../../services/article.service';
const Dashboard = (props) => {
const [content, setContent] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(10);
const filter = props.location.data;
if (filter) {
useEffect(() => {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter);
setContent(res.data);
};
fetchFiltered();
}, []);
} else {
useEffect(() => {
const fetchPosts = async () => {
const res = await ArticleService.articles();
setContent(res.data);
};
fetchPosts();
}, []);
}
let counter = content.length;
// Get current posts
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = content.slice(indexOfFirstPost, indexOfLastPost);
// Change page
const handlePageChange = (pageNumber) => {
setCurrentPage(pageNumber);
};
const render = (item, index) => {
return (
<tr key={index}>
<td className='text-center'>
<div key={item.id}>
<img
src={`${item.pictures}`}
alt='slika artikla'
className='rounded'
></img>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<h4>{item.descr}</h4>
<br></br>
<h6 className='text-left'>Number of m2: {item.sqm}m2</h6>
<div className='text-left'>
<small className='text-left'>
{' '}
<a href={item.link} target='_blank' rel='noopener noreferrer'>
Show on website
</a>
</small>
</div>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<h4>{item.price}</h4>
<small className='text-left'>Price per m2: {item.ppm2}</small>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<Link to={`/article/${item.id}`}>
<h4>Show</h4>
</Link>
</div>
</td>
</tr>
);
};
return (
<div>
<div className='container'>
<h4 className='text-center'>
Number {counter}
</h4>
<div className='table-responsive'>
<table className='table'>
<thead className='thead-dark'>
<tr>
<th className='text-center' scope='col'>
Pic
</th>
<th className='text-center' scope='col'>
Description
</th>
<th className='text-center w-25' scope='col'>
Price
</th>
<th className='text-center' scope='col'>
Show offer
</th>
</tr>
</thead>
<tbody>{currentPosts.map(render)}</tbody>
</table>
</div>
</div>
<nav>
<div className='w3-bar w3-xlarge'>
<ul className='pagination justify-content-center'>
<li className='page-item'>
<Pagination
hideDisabled
hideNavigation
hideFirstLastPages
currentPage={currentPage}
itemsCountPerPage={10}
totalItemsCount={content.length}
pageRangeDisplayed={indexOfLastPost}
onChange={handlePageChange}
/>
</li>
</ul>
</div>
</nav>
</div>
);
};
export default Dashboard;
Thanks! :D
Basic answer, no, you cannot conditionally call useEffect. You must put the conditional logic inside the useEffect callback.
const filter = props.location.data
useEffect(() => {
if (filter) {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter)
setContent(res.data)
}
fetchFiltered()
} else {
const fetchPosts = async () => {
const res = await ArticleService.articles()
setContent(res.data)
}
fetchPosts()
}
}, [filter, setContent, ArticleService.filterByName, ArticleService.articles])
Hooks in React do not really follow the standard rules of javascript. There are performance reasons around the way they have to be implemented, often some caching is done to stop excess code being executed every time a render pass is done.
The useEffect hook will only run its callback function during a render where one of the values in the dependency array (the second arg of useEffect) has changed. It's standard to put in all external values that could change. Thus when the value of filter changes, the app will rerender, the useEffect will do a comparision, realise that something has changed and run it's call back again which will then perform the if statement.
You can read more about this in the performance part of the docs
In addition to the above answer,from the official documentatation
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.
I have a react component which manage user logging in and out, when user type email and password in the login field the whole component (Navbar) re-render to Dom in every keystroke unnecessarily thus reduces speed.
How can I prevent Navbar from re-rendering when user type their credential in login fild ?
import React, { useContext,useState } from 'react';
import { Postcontext } from '../contexts/Postcontext';
import axios from 'axios';
const Navbar = () => {
const { token,setToken } = useContext(Postcontext);
const [email,setEmail] = useState(''); **state manages user email for login**
const [password,setPassword] = useState(''); **state manages user password for login**
const[log,setLog] = useState(true) **state manages if user logged in or not based on axios post request**
const login=(e)=>{
//function for login using axios
})
}
const logout=(e)=>{
//function for logout using axios
}
return (
<div className="navbar">
{log?(
<form>
<input value={email} type="text" placeholder="email" onChange={(e)=>setEmail(e.target.value)}/>
<input value={password} type="text" placeholder="password" onChange={(e)=>setPassword(e.target.value)}/>
<button onClick={login}>login</button>
</form>
):(
<button onClick={logout}>logout</button>
)}
</div>
);
}
export default Navbar;
It is because it is same component which needs re-render to reflect input text changes. If you want your email to change but not effect Navbar then create a child component and move inputs into that component, manage input values using useState() there in child component and when you finally submit and user is logged in then you can either update some global state like redux store or global auth context to reflect and rerender Navbar.
So, I had the same issue and I was able to solve it using useRef and useCallback and I will try to explain in Q&A form. Sorry if I am not that clear, this is my first StackOverFlow comment and I am a beginner in React :)
Why useRef?
React re-renders every time it sees a component has updated by checking if previous and current object are same or not. In case of useRef it checks the object Id only and not the content inside it i.e. value of current inside the Ref component. So if you change the value of current React will not consider that. (and that's what we want)
Why useCallback?
Simply because it will run only when we call it or one (or more) of the dependencies have changed. As we are using Ref so it won't be called when the current value inside it has changed.
More info: https://reactjs.org/docs/hooks-reference.html
Based on above info your code should look like this (only doing login part):
import React, { useContext, useRef } from 'react';
const App = () => {
const emailRef = useRef(null);
const passwordRef = useRef(null);
const logRef = useRef(null);
const loginUpdate = useCallback( async (event) => {
event.preventDefault();
// Your logic/code
// For value do:
// const email = emailRef.current.value;
}, [emailRef, passwordRef, logRef]);
return (
<div className="navbar">
{log?(
<form>
<input
ref={emailRef}
type="text"
placeholder="email"
/>
<input
ref={passwordRef}
type="text"
placeholder="password"
/>
<button onClick={loginUpdate}>login</button>
</form>
):(
// Not doing this part because I am lazy :)
<button onClick={logout}>logout</button>
)}
</div>
);
}
export default App;
Had a few typos. It works for me
https://codesandbox.io/s/cold-sun-s1225?file=/src/App.js:163-208
import React, { useContext,useState } from 'react';
// import { Postcontext } from '../contexts/Postcontext';
// import axios from 'axios';
const App = () => {
// const { token,setToken } = useContext();
const [email,setEmail] = useState('');
const [password,setPassword] = useState('');
const[log,setLog] = useState(true)
const login=(e)=>{
//function for login using axios
}
const logout=(e)=>{
//function for logout using axios
}
return (
<div className="navbar">
{log?(
<form>
<input value={email} type="text" placeholder="email" onChange={(e)=>setEmail(e.target.value)}/>
<input value={password} type="text" placeholder="password" onChange={(e)=>setPassword(e.target.value)}/>
<button onClick={login}>login</button>
</form>
):(
<button onClick={logout}>logout</button>
)}
</div>
);
}
export default App;
I'm using redux forms (just trying to learn). And axios to get my account data and populate it into form.
I've made a sandbox: https://codesandbox.io/s/mzlrv1n988
From example: https://redux-form.com/7.3.0/examples/initializefromstate/ everything seems to be clear and obvious. But it has nothing to deal with a real application: I refactored code a bit and i have top-level components for add && edit actions, who deal with server data and data from form. And I have a form component: it's goal is a form UI && validations.
How can I populate my form with data in top level component?
I mean this part of code in EditForm component:
getAccount = () =>
axios.get("https://jsonplaceholder.typicode.com/posts/1").then(Account => {
loadAccount(Account);
});
also form component:
import React from "react";
import { connect } from "react-redux";
import { Field, reduxForm } from "redux-form";
import { load as loadAccount } from "./account";
const data = {
// used to populate "account" reducer when "Load" is clicked
body: "testtest",
title: "yep!"
};
let AccountForm = props => {
const { handleSubmit, load, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<div>
<button type="button" onClick={() => load(data)}>
Load Account (example)
</button>
</div>
<div>
<label>body</label>
<div>
<Field name="body" component="input" type="text" placeholder="body" />
</div>
</div>
<div>
<label>title</label>
<div>
<Field
name="title"
component="input"
type="text"
placeholder="title"
/>
</div>
</div>
<div>
<button type="submit" disabled={pristine || submitting}>
Submit
</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Undo Changes
</button>
</div>
</form>
);
};
// Decorate with reduxForm(). It will read the initialValues prop provided by connect()
AccountForm = reduxForm({
form: "initializeFromState" // a unique identifier for this form
})(AccountForm);
// You have to connect() to any reducers that you wish to connect to yourself
AccountForm = connect(
state => ({
initialValues: state.account.data // pull initial values from account reducer
}),
{ load: loadAccount } // bind account loading action creator
)(AccountForm);
export default AccountForm;
If this can be done without using redux schema - it's even better, I do not need a huge part of logic here...
Also one small question: how to disable 'submit' button, while data is loading from the server?
If you look at your sandbox with the redux devtools you can see that the state is still empty after the axios call completes. This is because you are directly calling the reducer function instead of passing via a dispatch.
I edited the pen to illustrate the solution: https://codesandbox.io/s/o94p6ono3z
For the disable button you have to handle the usual redux process yourself: set a variable in the reducer before axios runs, unset it when axios is done. Then pass down that variable as you normally do in react/redux. Then set the button as disabled if that variable is true.
Meaning you do it like in a normal redux context no matter if you have a redux-form. What redux-form offers is the submitting variable that you can use to disable the button while the form is submitting.
import React from 'react';
import { observer } from "mobx-react"
import { inject } from "mobx-react"
import { deleteTradeData } from "../Actions/Actions"
import axios from "axios"
#observer
export default class TradeTable extends React.Component {
componentDidMount() {
//Adding data of db into mobx store
axios.get(`http://localhost:8091/trade`)
.then(res => {
this.props.store.arr = res.data;
console.log(res.data ,"resssssdata")
})
}
delete = (id) => {
console.log(id + "wdccerfec")
deleteTradeData(id);
window.location.reload();
}
edit = (id) => {
console.log(id + "iddddddddddddddddddddd")
this.props.store.editId = id;
this.props.store.formToggleFlag = false;
// alert("hi")
// window.location.reload();
}
render() {
var tableData = this.props.store.arr;
return <div className="panel panel-default">
{this.props.store.newTradeRender}
<div className="panel-body tradeComponent div-background table-responsive">
<table className="table table-striped tb div-lightbackground">
<thead className="thead-dark ">
<tr>
<th>Date</th>
<th>Commodity</th>
<th>Side</th>
<th>Qty (MT)</th>
<th>Price (MT)</th>
<th>Counterparty</th>
<th>Location</th>
<th></th>
</tr>
</thead>
<tbody>
{
tableData.map((tableItem, index) => {
return (
<tr key={index * Math.random()}>
<td>{tableItem.tradeDate}</td>
<td>{tableItem.commodity}</td>
<td>{tableItem.side}</td>
<td>{tableItem.quantity}</td>
<td>{tableItem.price}</td>
<td>{tableItem.counterparty}</td>
<td>{tableItem.location}</td>
<td>
<button type='submit' className="btn btn-primary table-style" onClick={this.delete.bind(this, tableItem.id)} >
<span className="glyphicon glyphicon-trash" aria-hidden="true"></span>
</button>
</td>
<td>
<button type='submit' className="btn btn-primary table-style edit edit-button" onClick={this.edit.bind(this, tableItem.id)} >
<span className="glyphicon glyphicon-pencil selected-glyph edit-pencil" aria-hidden="true"></span>
</button>
</td>
</tr>)
})
}
</tbody>
</table>
</div>
</div>
}
}
The above codes get rendered when an action in other component is fired (which is my logic).
The problem is that the render of the trade table component doesn't results in updating of the table data.Now this behavior is expected because the component did mount is called after render hence since the store data is not updated so trade table is not updated.
But i am unable to solve this problem. I tried component will mount and will update but that is causing some strange behavior ( a loop of updating table runs ) which cause my system & browser to freeze .So i cant not use that.Is there any logic or alternative?
In React JS you cannot change the props by doing this.props.store.arr = res.data;. This is because in React JS there is unidirectional flow of data, which means data can only flow in one direction and that is from parent to child. So the only way to change the props is by changing the value that is passed down by the parent.
Also ReactJS re-renders only on certain conditions. One is when reactJS state changes and another is by using shouldComponentUpdate. And you have to change React state using setState. If you change the state like this.state.foo = "bar" react will not re-render.