How to get items so that it is not null (useEffect)? - reactjs

How to get items so that it is not null? I'm trying to write the values ​​that I get through useEffect to sidebat -> items, but I get null, in general, is it mandatory to use 2 useEffect? Maybe you can somehow do it through one, so as not to duplicate the code?
const [sidebarItemsLeagues, setSidebarItemsLeagues] = useState(null);
const [sidebarItemsCountries, setSidebarItemsCountries] = useState(null);
const [sidebars] = useState({
leagues: {
title: "TITLE 1",
items: sidebarItemsLeagues // null...
},
countries: {
title: "TITLE 2",
items: sidebarItemsCountries // null...
}
});
useEffect(() => {
api.getTestLeagues()
.then(data => setSidebarItemsLeagues(data)); // for items 1 (not null)
}, []);
useEffect(() => {
api.getTestCountries()
.then(data => setSidebarItemsCountries(data)); // for items 2 (not null)
}, [])

If sidebars is a computed value, then don't create a state variable for it. By using useState you specify only what it's initial value is, and then any updates need to be done by calling setSidebars, which you never do.
const sidebars = {
leagues: {
title: "TITLE 1",
items: sidebarItemsLeagues
},
countries: {
title: "TITLE 2",
items: sidebarItemsCountries
}
}

2 useEffect is not mandatory, you can do both fetches one by one ... but you probably want to run them in parralel, not sequentially.
You can block rendering with empty data by
<>
{sidebarItemsLeagues && sidebarItemsCountries && (
<LeaguesList data={sidebarItemsLeagues} />
<Countries data={sidebarItemsCountries} />
)}
</>

Related

how to set checkbox in useState and remove duplicate checkbox value in react

I have two problems with the checkbox
1- When I click on each checkbox, they are correctly added to plantsFill, but duplicate items are also saved. I want it to be deleted if the plant_id is duplicated.
2- When I uncheck it, that item is still stored in my plantsFill and is not deleted
const allPlants = [
{
"plant_id": 1,
"title": "title 1",
},
{
"plant_id": 2,
"title": "title 2",
},
{
"plant_id": 3,
"title": "title 3",
},
];
const handleCheckClick = (e) => {
setPlantsFill([...plantsFill, {plant_id: e.target.value}]);
};
{allPlants.map((t) => {
return (
<div key={t.plant_id}>
<label htmlFor="">
<input
type="checkbox"
value={t.plant_id}
onChange={handleCheckClick}
/>
{t.title}
</label>
</div>
);
})}
Your problem is caused by your spreadings, who recreate the same array with a new value without deleting the old one.
If you really want to use spread syntax, you will need to deal with sorting, because the new value will be added last in the array (so even if you filter(), your modified checkbox will end up changing it's position on the list). I recommend you to simply use map() to edit them.
You can add a checked property to each of your checkbox, and set their value using event.target.checked or their own checked property, like:
const [allPlants, setPlantFill] = React.useState([{
"id": 1,
"title": "title 1",
checked: false
}, {
"id": 2,
"title": "title 2",
checked: false
}, {
"id": 3,
"title": "title 3",
checked: false
}]);
const handleCheckClick = (id) => {
setPlantsFill(allPlants.map((checkbox) => checkbox.id === id ? { ...checkbox, checked: !checkbox.checked } : checkbox));
};
return (
{allPlants?.map((checkbox) => (
<div key={checlbox.id}>
<label htmlFor="">
<input
type="checkbox"
value={checkbox.checked}
onChange={() => handleCheckClick(checkbox.id)}
/>
{checkbox.title}
</label>
</div>
)) ?? (
<div>
Loading...
</div>
)}
);
The problem is with handle handleCheckClick function, in your code you are concating items instead of updating their value. One more thing onChange is called for checking and unchecking so you need to check (no pan intended) what is the new status to see if you need to add to the array or remove it from the array.
example:
const handleCheckClick = (e) => {
if (e.checked) {
setPlantsFill(previousPlantsFill => [...previousPlantsFill, {plant_id: e.target.value}]);
return;
}
setPlantsFill(previousPlantsFill => previousPlantsFill.filter(plantFill => plantFill.plant_id !== e.target.value));
};
and a more clean and robust example (checking if value already exists before adding, using useCallback, and deconstruct syntax)
const handleCheckClick = useCallback((e) => {
if (e.checked) {
setPlantsFill(previousPlantsFill => {
if (previousPlantsFill.some(({plant_id}) => plant_id === e.target.value)) {
return previousPlantsFill
}
return [...previousPlantsFill, {plant_id: e.target.value}]
});
return;
}
setPlantsFill(previousPlantsFill => previousPlantsFill.filter(({plant_id}) => plant_id !== e.target.value));
}, []);
Assuming allPlants do not change since you define it as a const and you just want to keep track of what plants are being checked. You can keep a single array of all the plant_id's.
const [plantsFill, setPlantsFill] = useState([]);
const handleCheckClick = (e) => {
const value = e.target.value;
const hasPlant = e.target.checked;
setPlantsFill((prevPlantsFill) => {
// if in the array return the array without the value
if (hasPlant) return prevPlantsFill.filter((plant) => plant !== value);
// else return the old array with the new value
return [...prevPlantsFill, value];
});
};
If you want to get all the plant objects which are checked you can do it like so
const allCheckedPlants = allPlants.filter((plant) =>
plantsFill.includes(plant.plant_id)
);
Hope this helps you with your project!

REACT- Displaying and filtering specific data

I want to display by default only data where the status are Pending and Not started. For now, all data are displayed in my Table with
these status: Good,Pending, Not started (see the picture).
But I also want to have the possibility to see the Good status either by creating next to the Apply button a toggle switch : Show good menus, ( I've made a function Toggle.jsx), which will offer the possibility to see all status included Good.
I really don't know how to do that, here what I have now :
export default function MenuDisplay() {
const { menuId } = useParams();
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus] = useState([]);
useEffect(() => {
axios.post(url,{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Status",
accessor: (row) => row.status
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
return (
<div>
<button type="button" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
<Table
data={matchData}
columns={data}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
{
"menuId": 1,
"_id": "123ml66",
"name": "Pea Soup",
"description": "Creamy pea soup topped with melted cheese and sourdough croutons.",
"dishes": [
{
"meat": "N/A",
"vegetables": "pea"
}
],
"taste": "Good",
"comments": "3/4",
"price": "Low",
"availability": 0,
"trust": 1,
"status": "Pending",
"apply": 1
},
//...other data
]
Here my CodeSandbox
Here a picture to get the idea:
Here's the second solution I proposed in the comment:
// Setting up toggle button state
const [showGood, setShowGood] = useState(false);
const [menus, setMenus] = useState([]);
// Simulate fetch data from API
useEffect(() => {
async function fetchData() {
// After fetching data with axios or fetch api
// We process the data
const goodMenus = dataFromAPI.filter((i) => i.taste === "Good");
const restOfMenus = dataFromAPI.filter((i) => i.taste !== "Good");
// Combine two arrays into one using spread operator
// Put the good ones to the front of the array
setMenus([...goodMenus, ...restOfMenus]);
}
fetchData();
}, []);
return (
<div>
// Create a checkbox (you can change it to a toggle button)
<input type="checkbox" onChange={() => setShowGood(!showGood)} />
// Conditionally pass in menu data based on the value of toggle button "showGood"
<Table
data={showGood ? menus : menus.filter((i) => i.taste !== "Good")}
/>
</div>
);
On ternary operator and filter function:
showGood ? menus : menus.filter((i) => i.taste !== "Good")
If button is checked, then showGood's value is true, and all data is passed down to the table, but the good ones will be displayed first, since we have processed it right after the data is fetched, otherwise, the menus that doesn't have good status is shown to the UI.
See sandbox for the simple demo.

How can I change the array key value before passing to the state in react?

Question, I have this state coming from the backend It's a array of messages that will be store in state using useState. This state will be pass on in the child component. The problem is I want to change value of a specific key before storing it into the state.
Sample
Messages Array sample data
const messages = [
{
value: 'sample value one',
status: false,
},
{
value: 'sample value two',
status: false,
},
];
UseSelector
const messageGetById = useSelector((state) => state.messageGetById);
const { message } = messageGetById;
UseEffect
useEffect(() => {
if (message) {
setCurrentMessage(message);
}
}, [message]);
The output that I want is before passing the message into setCurrentMessage, all the value of status will be change to true.
Thanks!
You can use map method to map thought the array and change the status to true.
useEffect(() => {
if (message) {
const newMessages = messages?.map((mess) => {
return {...mess, status: true}})
setCurrentMessage(newMessages);
}}, [message]);
Set the mapped state with useEffect
useEffect(() => {
const data = [...message];
if (data.length > 0) {
data.map((ele) => ({
value: "YOUR CHANGED VALUE",
status: ele.status,
}));
setCurrentMessage(data);
}
}, [message]);

How can i auto refresh or render updated table data form database in material UI table after doing any operation in React?

Here useVideos() give us all videos form database. After adding a new video the new entry is not append in the Material UI table , but if I refresh the page then it's showing that new entry. Now I want to show this new entry after add operation. Please help me to do this! Thanks in Advance.
const initialState = [{}];
const reducer = (state, action) => {
switch (action.type) {
case "videos":
const data = [];
let cnt = 1;
action.value.forEach((video, index) => {
data.push({
sl: cnt,
noq: video.noq,
id: index,
youtubeID: video.youtubeID,
title: video.title,
});
cnt = cnt + 1;
});
return data;
default:
return state;
}
};
export default function ManageVideos() {
const { videos, addVideo, updateVideo, deleteVideo } = useVideos("");
const [videoList, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({
type: "videos",
value: videos,
});
}, [videos]);
const columns = [
{ title: "Serial", field: "sl" },
{ title: "Title", field: "title" },
{ title: "Number of Qusetion", field: "noq" },
{ title: "Youtube Video Id", field: "youtubeID" },
];
return (
<div>
<MaterialTable
title="Videos Table"
data={videoList}
columns={columns}
editable={{
onRowAdd: (newData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
addVideo(newData);
resolve();
}, 1000);
}),
onRowUpdate: (newData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
updateVideo(newData);
resolve();
}, 1000);
}),
}}
/>
</div>
);
}
Since the information provided is a bit lacking, I'll assume that the useEffect hook is not working when you update your videos (check it with consle.log("I'm not working") and if it does work then you can just ignore this answer).
You can define a simple state in this component, let's call it reRender and set the value to 0, whenever the user clicks on the button to add a video you should call a function which adds 1 to the value of reRender (()=>setReRender(prevState=>prevState+1)). In your useEffect hook , for the second argument pass reRender. This way, when the user clicks to submit the changes , reRender causes useEffect to run and dispatch to get the new data.
If this doesn't work , I have a solution which takes a bit more work. You will need to use a state manager like redux or context api to store your state at a global level. You should store your videos there and use 1 of the various ways to access the states in this component (mapStateToProps or store.subscribe() or ...). Then pass the video global state as the second argument to useEffect and voilà, it will definitely work.

How to deleted elements of a list who are meeting a certain condition on React js

I would like to delete all completed items from the to-do list when pressing a button.
How could I do that? My useState is like this :
const [todos, setTodos] = useState([
{ id: 58477, text: "Wash dishes", done: false },
{ id: 64851, text: "Bake a cake", done: true },
{ id: 59858, text: "Make a website", done: true },
])
And my code is like that:
export default function TodoListItem(){
const [ todos, setTodos ] = useTodosContext()
function deleteTodo(todo) {
console.log("Clear completed")
}
return(
<div>
{todos.map(todo => <li><input type="checkbox" className="roundCheckBox"/>{todo.text}</li>)}
<button onClick={() => deleteTodo(todo)}>CLEAR COMPLETED</button>
</div>
)
}
You could use a higher order array method (like filter / forEach) on the todos-state array to segregate the done todos and then remove it from the state , updating state automatically makes react render the todoItems again this time with the new state i.e. without the completed items as you wanted
let todos = [
{
done: true,
name: "i need to be removed"
},
{
done: true,
name: "useless"
},
{
done: false,
name: "i need to be kept"
},
{
done: false,
name: "useful"
}];
let elements_to_delete = todos.filter(todo => todo.done);
let elements_to_keep = todos.filter(todo => !todo.done);
console.log("\n Items to keep are :-");
console.log(elements_to_keep);
console.log("\n Items to delete are :-");
console.log(elements_to_delete);
The above snippet shows how you can filter out done item then all you need to do is use setTodos(elements_to_keep)in the deleteTodo function because elements_to_keep is also an array that can replace the previous array.
You can try this:
// replace your function deleteTodo with this:
const deleteTodo = useCallback((d_todo) => {
setTodos(todos => todos.filter(todo => d_todo.id !== todo.id))
}, [])
This filters-out the todo being deleted.

Resources