How can I memoize redux state? - reactjs

I'm struggling with useSelector in component and I would like to memoize the value of useSelector to just read the value from the cache if other states changes and don't get the state from redux for every component rerender.
Here's the selector which I've used in parent component:
const campaignListSelector = createSelector(
(state) => state.campaignReducer,
campaignReducer => campaignReducer.campaignList ? campaignReducer.campaignList.campaigns : []
);
const campaigns = useSelector(campaignListSelector);
Also I have a component which is called Button:
const Button = React.memo(({ onClick, counter, title }) => {
return <button onClick={onClick}>{counter}</button>
})
And this is the code which I've developed in parent component:
const ParentComponent = (props) => {
const dispatch = useDispatch()
const [state, setState] = React.useState({
pageNo: 1,
counter: 0,
})
useEffect(() => {
console.log("state.pageNo", state.pageNo)
dispatch(CampaignActionCreators.fetchCampaignList(state.pageNo, -1, null, null, 'All', '', 'All'));
}, [state.pageNo])}
const campaignListSelector = createSelector(
(state) => state.campaignReducer,
campaignReducer => campaignReducer.campaignList ? campaignReducer.campaignList.campaigns : []
);
const campaigns = useSelector(campaignListSelector);
const table = React.useMemo(() => {
console.log(campaignList)
return <table className="table table-list-layout">
<thead>
<tr>
<th width="15%">
{Utils.i18n('campaign', true)}
</th>
<th className="text-center">
{Utils.i18n('campaignSetting', true)}
</th>
</tr>
</thead>
<tbody>
{campaignList && campaignList.map((campaign, index) => (
<tr key={index}>
<td>
<LinkButton to={`/Campaign/View?id=${campaign.id}`}
text={campaign.title}
/>
<td className="text-center">
-
</td>
</tr>
))}
</tbody>
</table>
})
const onClick = () =>
setState({
...state,
counter: state.counter + 1
})
return <React.Fragment>
<Button onClick={onClick} counter={state.counter} />
{table}
</React.Fragment>
My problem is that every time I click on the Button compponent, table value which I've used it with React.memo will be re-rendered and console.log(campaignList) will show a line on the console. How can I prevent table from re-rendering while I just click on button and inline state of the ParentComponent will be changed? How can I memoize the value of campaigns?

Related

How to save changes of a input in table row as single and multiple?

I have some inputs in every row(PreviewItemRow.js) of Table component. I get the data from Redux store. I keep PreviewItemRow changes as internal state. There is also a save button in every button whose onClick event makes an api call to server.
Problem is I want user to save(make api call) his changes as batch requests and also use should be able to save as individual row.
If I reflect changes directly to redux store changes state in redux whenever user presses a button in keyboard, I wont be able to be sure if changes reflected to server.
If I keep the name as component internal state, I can not track changes from SaveAll button.
So how can I Implement to save changes from a button individual row and a button in parent component ?
Parent Table Component
const ParentTableComp = (props) => {
const cases = useSelector(store => store.taskAppReducer.Case.cases);
const handleSaveAllClick = () => {
dispatch(previewBulkSave({
taskId: selectedTask.taskId,
caseData: cases.map(item => ({
name: item.caseName,
}))
}))
.then(() => {
saveSuccess("All saved.");
})
.catch((err) => {
saveError(err);
});
};
return (
<div>
<Button
type='button'
color='primary'
onClick={handleSaveAllClick}
>
Save All
</Button>
<Table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{cases.map((item, index) => (
<tr key={item.caseId}>
<PreviewCaseItem
case={item}
/>
</tr>
))}
</tbody>
</Table>
</div>
);
};
This is the Row component.
const PreviewItemRow = (props) => {
const [name, setName] = useState(props.case.name)
const dispatch = useDispatch();
const handleSaveButtonClick = () => {
dispatch(saveCase({
taskType: taskType,
updatedCase: {
...props.case,
name
},
}))
.then(() => {
saveSuccess("Case Updated");
})
.catch((err) => {
saveError(err);
});
};
const handleNameChange = (event) => {
setName(event.target.value)
}
return (
<div>
<td style={{ width: 100 }}>
<Input
type={"text"}
id={`name-${props.case.caseId}`}
value={name}
onChange={handleNameChange}
/>
</td>
</div>
);
};

redux action : pass parameter in axios request with hooks

I'm working on a React app with Redux (thunk).
I have a list of users in this table :
function DataUsers() {
const users = useSelector((state: any) => state.userReducer.infos);
const now = 60;
const history = useHistory();
return (
<div className="tableCardDataUsers">
<Table className="table" bordered hover responsive>
<thead>
<tr className="tableHeader">
<th>ID</th>
<th>Nom</th>
<th>Email</th>
<th>Date de création</th>
<th>Numéro de téléphone</th>
<th>Pourcentage</th>
<th>Catégories</th>
</tr>
</thead>
<tbody>
{users.slice(0, 20).map((item: any, index: number) => (
<tr
className="redirectUser"
onClick={() => history.push("/Utilisateurs")}
key={index}
>
<td>{item.id}</td>
<td>
{item.nom} {item.prenom}
<br></br>Tcheker
</td>
<td>{item.email}</td>
<td>18/10/1998</td>
<td>{item.tel}</td>
<td>
<ProgressBar now={now} label={`${now}%`} />
</td>
<td className="tableCategorie">
<Button
name="VDR"
name2=""
class="catButtonMini"
color="orange"
onclick={() => {}}
type="button"
data=""
image=""
/>
</td>
</tr>
))}
</tbody>
</Table>
</div>
);
}
I want to get the id of the user onClick and then dispatch an action to display the user infos on a new page.
I made this action to get the user infos :
export const getUserById = (id_user: number | string) => {
return async (dispatch: any) => {
dispatch(isLoading(true));
try {
const res = await axios.get(`users/getUserById/${id_user}`);
dispatch(isLoading(false));
dispatch({ type: GET_USER_BY_ID, payload: res.data });
} catch (err) {
console.log(err);
dispatch(isLoading(false));
}
};
I'm usually using useSelector in my component to get datas like so :
const user = useSelector((state: any) => state.userReducer.getUserById);
I'm wondering what is the best way to pass the id_user in my action when clicking on a user cell to display it on a new user page.
Thank you
The issue is you are trying to use useSelector to dispatch an action . useSelector is to read the data from the store .
To dispatch an action in redux you need to use useDispatch . So you can change your code in your component as
import { useDispatch } from "react-redux";
import {getUserById } from 'your path
Now inside your component you can do
const UserInfo = () => {
const dispatch = useDispatch();
// get the user id , either from the store or from url params
useEffect(() => {
dispatch(getUserById(passyourId));
}, [dispatch]);
return <> Your User Info component code goes here </>;
};
Reference
useSelector
useDispatch

searchfilter with using react hook(useEffect / useState)

I am trying to create a searchBar.
When I type some value on input, I would like my listitems from github api to be re-listed with the value on searchBar.
import './App.css';
function App() {
const [datas, setDatas] = useState([]);
const [userid, setUserid] = useState('');
const inputChanged = (event) => {
setUserid(event.target.value)
}
const searchBar = () => {
}
useEffect(() => {
fetch('https://api.github.com/search/repositories?q=react')
.then(response => response.json())
.then(data => {
setDatas(data.items)
})
},[])
return (
<div className="App">
<h1>Repositories</h1>
<input id="searchInput"type="text" placeholder="search" name="search" value={userid} onChange={inputChanged}/>
<button onClick={searchBar}>Search</button>
<table>
<tbody>
<tr>
<th>Name</th>
<th>URL</th>
</tr>
{
datas.map((data, index) =>
<tr key={index}>
<td>{data.full_name}</td>
<td><a href={data.html_url}>{data.html_url}</a></td>
</tr>
)
}
</tbody>
</table>
</div>
);
}
export default App;
Here is my code and the image of the localhost
useEffect has an array at the end, when left empty what's in useEffect only update once. You can add variables to that array, to update when that variable changes.
Here you need to write: useEffect(your function,[userid]);

Is correct this update state in react?

Input event
public handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ emptyFields: false, error: false, loading: false });
this.setState({ product: { ...this.state.product, [e.target.name]: e.target.value } });
}
Map test
<tbody>
{this.props.products.map((prod: IProduct) =>{
console.log('remap ???')
return (<tr key={prod.id}>
<td>{prod.id}</td>
<td>{prod.name}</td>
<td>{prod.price}</td>
</tr>)
}
)}
</tbody>
When I change the input, this map is made again as many times as I change the input.
When you change the state, react will call the render method again.This is expected.
Break out parts of your html in seperate components and make the components pure. This will prevent needless re render of DOM. However; at the moment it won't re render dom because virtual DOM compare of React will optimize. You will get in trouble if each row gets props that are recreated every time the parent renders, like not using useCallback for the delete callback:
//use React.memo to create a pure component
const ProductRow = React.memo(function ProductRow({
product: { id, name },
onDelete,
}) {
console.log('generating jsx for product:', id);
return (
<tr>
<td>{id}</td>
<td>{name}</td>
<td>
<button onClick={() => onDelete(id)}>X</button>
</td>
</tr>
);
});
//React.memo is pure component, only re renders if
// props (=products or onDelete) change
const Products = React.memo(function Products({
products,
onDelete,
}) {
return (
<table>
<tbody>
<tr>
<th>id</th>
<th>name</th>
</tr>
{products.map((product) => (
<ProductRow
key={product.id}
product={product}
onDelete={onDelete}
/>
))}
</tbody>
</table>
);
});
const id = ((id) => () => ++id)(0); //create id
const AddProduct = React.memo(function AddProduct({
onAdd,
}) {
const [name, setName] = React.useState('');
//no use to use useCallback, this function re creates
// when name changes
const save = () => {
onAdd(name);
setName('');
};
return (
<div>
<label>
name:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
<button onClick={save}>save</button>
</div>
);
});
const App = () => {
//initial products state
const [products, setProducts] = React.useState(() => [
{ id: id(), name: 'first product' },
{ id: id(), name: 'second product' },
]);
//use useCallback to create an add function on mount
// this function is not re created causing no needless
// re renders for AddProduct
const onAdd = React.useCallback(
(name) =>
setProducts((products) =>
products.concat({
id: id(),
name,
})
),
[]
);
//delete function only created on mount
const onDelete = React.useCallback(
(id) =>
setProducts((products) =>
products.filter((product) => product.id !== id)
),
[]
);
return (
<div>
<AddProduct onAdd={onAdd} />
<Products products={products} onDelete={onDelete} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React refresh the page after delete button

The delete function of my app is working fine, however it requires the user to manually refresh the page after the user click the delete button in order to see the new list of elements in my database. I would like to automatically refresh after the click event. I am using react hooks for this projects. However, I found one solution if I remove useEffect's [] but in my backend it shows, its requesting crazily. I don't know, is it wise to remove useffect's [ ]?
Here is the component where it fetches data from backend and passes the props to another component
import React, { useState, useEffect } from "react";
import axios from "axios";
import Table from "../Table/Table";
import "./Display.css";
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const [searchItem, setsearchItem] = useState({
item: ""
});
const Search = e => {
setsearchItem({ item: e.target.value });
};
useEffect(() => {
axios
.get("/students")
.then(response => {
setState({
students: response.data.students,
count: response.data.count
});
})
.catch(function(error) {
console.log(error);
});
}, []); //If I remove this square brackets, it works
const nameFilter = state.students.filter(list => {
return list.name.toLowerCase().includes(searchItem.item.toLowerCase());
});
return (
<div>
<h3 align="center">Student tables</h3>
<p align="center">Total students: {state.count}</p>
<div className="input-body">
<div className="row">
<div className="input-field col s6">
<input placeholder="search student" onChange={Search} />
</div>
</div>
</div>
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Date of birth</th>
<th>Address</th>
<th>Zipcode</th>
<th>City</th>
<th>Phone</th>
<th>Email</th>
<th colSpan="2">Action</th>
</tr>
</thead>
{nameFilter.map((object, index) => {
return (
<tbody key={index}>
<Table obj={object} /> //In here I am passing the props to the another component.
</tbody>
);
})}
</table>
</div>
);
};
export default Display;
This is second component which receives the props.
import React, { useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(console.log("Deleted"))
.catch(err => console.log(err));
};
return (
<React.Fragment>
<tr>
<td>{props.obj.name}</td>
<td>{props.obj.birthday}</td>
<td>{props.obj.address}</td>
<td>{props.obj.zipcode}</td>
<td>{props.obj.city}</td>
<td>{props.obj.phone}</td>
<td>{props.obj.email}</td>
<td>
<Link
to={"/edit/" + props.obj.id}
className="waves-effect waves-light btn"
>
Edit
</Link>
</td>
<td>
<button onClick={removeData} className="waves-effect red btn ">
Remove
</button>
</td>
</tr>
</React.Fragment>
);
};
export default Table;
The [] in the useEffect hook is a dependency array to trigger the effect to run. If you want to trigger the effect (without it going off mercilessly), you can create a new variable that triggers that effect to run.
import React, { useState, useEffect } from "react";
import axios from "axios";
import Table from "../Table/Table";
import "./Display.css";
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const [requestData, setRequestData] = useState(new Date());
const [searchItem, setsearchItem] = useState({
item: ""
});
const Search = e => {
setsearchItem({ item: e.target.value });
};
useEffect(() => {
axios
.get("/students")
.then(response => {
setState({
students: response.data.students,
count: response.data.count
});
})
.catch(function(error) {
console.log(error);
});
}, [requestData]);
const nameFilter = state.students.filter(list => {
return list.name.toLowerCase().includes(searchItem.item.toLowerCase());
});
return (
<div>
<h3 align="center">Student tables</h3>
<p align="center">Total students: {state.count}</p>
<div className="input-body">
<div className="row">
<div className="input-field col s6">
<input placeholder="search student" onChange={Search} />
</div>
</div>
</div>
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Date of birth</th>
<th>Address</th>
<th>Zipcode</th>
<th>City</th>
<th>Phone</th>
<th>Email</th>
<th colSpan="2">Action</th>
</tr>
</thead>
{nameFilter.map((object, index) => {
return (
<tbody key={index}>
<Table obj={object} setRequestData={setRequestData} />
</tbody>
);
})}
</table>
</div>
);
};
export default Display;
Then you can trigger it from your Table component:
import React, { useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(() => {
props.setRequestData(new Date());
})
.catch(err => console.log(err));
};
return (
<React.Fragment>
<tr>
<td>{props.obj.name}</td>
<td>{props.obj.birthday}</td>
<td>{props.obj.address}</td>
<td>{props.obj.zipcode}</td>
<td>{props.obj.city}</td>
<td>{props.obj.phone}</td>
<td>{props.obj.email}</td>
<td>
<Link
to={"/edit/" + props.obj.id}
className="waves-effect waves-light btn"
>
Edit
</Link>
</td>
<td>
<button onClick={removeData} className="waves-effect red btn ">
Remove
</button>
</td>
</tr>
</React.Fragment>
);
};
export default Table;
Not sure if helps but you can always remove the item from the current array, so a refresh is not needed, for example you can pass as props a function that receives an id and then filter the students array to exclude the element that matches with that id and then update the state with the new array and count properties, something like this
In your parent:
const Display = () => {
const [state, setState] = useState({ students: [], count: "" });
const deleteItem = (id) => {
const newStudents = state.students.filter(student => student.id !== id)
const newCount = newStudents.length;
setState({ students: newStudents, count: newCount })
}
// Rest of the code
}
Now pass that function to your child component.
<Table obj={object} deleteItem={deleteItem} />
In the child component just modify your removeData method to add the deleteItem prop:
const Table = props => {
const removeData = () => {
axios
.delete("/students/" + props.obj.id)
.then(console.log("Deleted"))
.catch(err => console.log(err));
// Now if your request succeeds call the function to remove the item from the students state array
props.deleteItem(props.obj.id);
};
// Rest of the code
}
I know this does not answer your question, but when you're working with react or it is better to do this computations and filters on the app side, like in this case that even though the record was deleted from the db we also removed the record from the student state object and there's no need to refresh the page.
Remember, you're creating a single page application, so we want the nicest experience for the user without refreshing the page for every action the user makes.
Have a look at this
import React, { useState } from "react";
const Display = () => {
const [refresh, setRefresh] = useState(false)
const delete=() => {
// ................. //delete logic
reload ? setRefresh(false) : setRefresh(true) //toggle just to change state
}
useEffect(() => {
}, [reload]); //inject as dependency
}

Resources