I have a checkbox component whose state is handled in Redux Toolkit. When checking one checkbox it's checking all other checkboxes in all rows, but I only want to check the checkbox I click on.
Here's my code below:
Checkbox Component:
export const Checkbox = (props) => {
const dispatch = useDispatch()
const handleCheckBox = () => {
dispatch(checkboxState)
}
const isChecked = useSelector((state) => (
state.isChecked
))
return (
<input type='checkbox' checked={isChecked} onChange={handleCheckBox}/>
)
}
Slice:
const rowState = {
data: [],
isChecked: false,
loading: false
}
export const rowSlice = createSlice({
name: 'rows',
initialState: rowState,
reducers: {
CHECK_ROWS(state) {
state.isChecked = !state.isChecked
},
})
export const checkboxState = rowSlice.actions.CHECK_ROWS()
Then I'm calling the checkbox component in my page:
const handleRows = (rowData) => {
return (
<tr>
<td>
<Checkbox />
</td>
//rest of the code
</tr>
return(
<Table>
{
dataSource.map((data) => (
handleRows(data)
))
}
</Table>
)
This is happening because you are keeping one variable isChecked for the entire component. To make it unique to each data, keep this as an array:
const rowState = {
data: [],
checkedData: [],
loading: false
}
Then, you should update the checkedData array accordingly. Check state will receive an id or an index and remove from checkedData if it is present in checkecData or add to checkedData if it is not present.
An example:
checkedData.includes(index) ? checkedData.filter((d,i) => i !== index) : [...checkedData, index]
Each checkbox should need an index, and the state should keep track of which checkbox that is checked or not.
import { useSelector, Provider, useDispatch } from "react-redux";
import { createAction, createReducer, configureStore } from "#reduxjs/toolkit";
const initialState = { checkboxes: [false, false, false] };
const index = createAction("toggle/index");
const toggleReducer = createReducer(initialState, (builder) => {
builder.addCase(index, (state, action) => {
state.checkboxes[action.payload] = !state.checkboxes[action.payload];
});
});
const store = configureStore({ reducer: toggleReducer });
const toggleIndex = (index) => {
return {
type: "toggle/index",
payload: index,
};
};
export const Checkbox = ({ index }) => {
const dispatch = useDispatch();
const handleCheckBox = () => {
dispatch(toggleIndex(index));
};
const isChecked = useSelector(({ checkboxes }) => checkboxes[index]);
return (
<input type="checkbox" checked={isChecked} onChange={handleCheckBox} />
);
};
const App = () => {
return (
<Provider store={store}>
<div>
<Checkbox index={0} />
<Checkbox index={1} />
<Checkbox index={2} />
</div>
</Provider>
);
};
export default App;
Related
I have been given the following task:
There an inpunt field with ADD button. You can create some text messages and post them press ADD. After you create the first one ADD SUBLIST and REMOVE buttons occure. When you press ADD SUBLIST you got a sublist on current list section and this sublist starts with input field and ADD button and able to create text messages and sublists as noted above. After sublist created the ADD SUBLIST BUTTON change for REMOVE SUBLIST.
Below my structure of listReducer.js:
const defaultState = {
elements: [{
id: 0,
text: '',
topPos: false,
downPos: false,
isInputField: true,
isDelButton: false,
isAddButton: true,
isSublist: false,
subList: []
}]
}
export const ADD_VALUE = 'ADD_VALUE'
export const REMOVE_VALUE = 'REMOVE_VALUE'
export const SET_UP = 'SET_UP'
export const SET_DOWN = 'SET_DOWN'
export const ADD_SUBLIST = 'ADD_SUBLIST'
export const listReducer = (state = defaultState, action) => {
switch (action.type) {
case ADD_VALUE:
return {
...state,
elements: [...state.elements, {
...state.elements.map(
(elem) => ({
...elem,
id: Date.now(),
text: action.payload,
isDelButton: true,
isAddButton: false,
isInputField: false
}))[0]
}]
}
case REMOVE_VALUE:
return {...state, elements: state.elements.filter(element => element.id !== action.payload)}
case ADD_SUBLIST:
return {
...state,
elements: [...state.elements, {
...state.elements.map(
(elem) => ({
...elem,
id: Date.now(),
isSubList: true,
subList: [defaultState] //have problem here
}))[0]
}]
}
default:
return state
}
}
export const addValueCreator = (payload) => ({type: ADD_VALUE, payload})
export const removeValueCreator = (payload) => ({type: REMOVE_VALUE, payload})
export const addSubListCreator = (payload) => ({type: ADD_SUBLIST, payload})
Item file
import React from 'react';
import MyInput from "../UI/MyInput";
import MyButton from "../UI/MyButton";
import MyLi from "../UI/MyLi";
import {useState} from "react";
import {useDispatch} from "react-redux";
import {addSubListCreator, addValueCreator, removeValueCreator} from "../reducer/listReducer";
function Item({element, children, ...props}) {
const [value, setValue] = useState('');
const dispatch = useDispatch();
const addElement = (value) => {
dispatch(addValueCreator(value));
};
const removeElement = (id) => dispatch(removeValueCreator(id))
const addSublist = () => {
const newSubElement = {id: Date.now()};
// const currentID = element.id;
dispatch(addSubListCreator(newSubElement))
}
//console.log('Item', element);
return (
<MyLi>
{element.isInputField ? <MyInput
type="text" value={value}
placeholder='Enter some text here'
onChange={event => setValue(event.target.value)}
/> : children}
<div>
{element.isSublist ? <MyButton>Delete Sublist</MyButton> : !element.isAddButton ? <MyButton
onClick={() => addSublist()}>Add
Sublist</MyButton> : null}
{element.isDelButton ? <MyButton
onClick={() => removeElement(element.id)}
>Remove</MyButton> : null}
{element.isAddButton ? <MyButton
onClick={() => {
addElement(value);
setValue('')
}}
>Add</MyButton> : null}
</div>
</MyLi>
);
}
export default Item;
ListItem file
import React, {useState} from "react";
import Item from "../Components/Item";
import {useSelector} from "react-redux";
const ListItem = () => {
const elements = useSelector((state) => state.elements);
//console.log('List elems ', elements)
return (
<ul>
{elements.map(
element => {
console.log('element subList', element.subList);
return (<Item element={element} key={element.id}>{element.text}
{element.subList.length > 0 ? element.subList.map(
sub => {
console.log('sub', sub.elements);
return (
<ul><Item key={sub.elements.id} element={sub.elements}>{sub.elements.text}</Item>
</ul>)
}).reverse() : null}
</Item>)
}).reverse()}
</ul>
);
};
export default ListItem;
How to create correct structure for this?
I have a store that includes booksSlice. booksSlice has two states: data and checked. The checked is an empty array. How can I get the list of checked items. The point is that the checked list in state base should be changed in order that I can use it in other components.
BooksSlice.jsx
import booksData from '../data/books';
const booksSlice = createSlice({
name: 'books',
initialState: {
data: booksData,
checked: [],
},
reducers: {
}
})
export const booksActions = booksSlice.actions;
export default booksSlice;
BooksList.jsx
import { useSelector } from "react-redux";
const BookList = () => {
const data = useSelector((state) => state.books.data);
const checked = useSelector((state) => state.books.checked);
const handleChoice = (event) => {
var updatedList = [...checked]
if(event.target.checked) {
updatedList = [...checked, event.target.value]
}else{
updatedList.splice(checked.indexOf(event.target.vaue, 1));
}
console.log(updatedList);
return updatedList;
}
return (
<div>
<h3>Books in our Store</h3>
<p>Please select the books you want</p>
<form>
{data.map((book) => {
return (
<div key={book.isbn}>
<label>
<input type="checkbox" onChange={handleChoice} value={book.title}/>
{book.title} - {book.author}
</label>
</div>
);
})}
</form>
</div>
);
};
export default BookList;
I started learning mobx and got stuck. Why when I change listItems, List doesn't re-render?
I have store:
export const listStore = () => {
return makeObservable(
{
listItems: [],
addItem(text) {
this.listItems.push(text);
}
},
{
listItems: observable,
addItem: action.bound
}
);
};
Component that adds text from input to store:
const store = listStore();
export const ListForm = observer(() => {
const [value, setValue] = useState();
return (
<>
<input type="text" onChange={e => setValue(e.target.value)} />
<button onClick={() => store.addItem(value)}>Add note</button>
</>
);
});
And I have a list component:
const store = listStore();
export const List = () => {
return (
<React.Fragment>
<ul>
<Observer>
{() => store.listItems.map(item => {
return <li key={item}>{item}</li>;
}
</Observer>
</ul>
<ListForm />
</React.Fragment>
);
};
I don't understand what's wrong. Looks like the list doesn't watch the store changing
codesandbox: https://codesandbox.io/s/ancient-firefly-lkh3e?file=/src/ListForm.jsx
You create 2 different instances of the store, they don't share data between. Just create one singleton instance, like that:
import { makeObservable, observable, action } from 'mobx';
const createListStore = () => {
return makeObservable(
{
listItems: [],
addItem(text) {
this.listItems.push(text);
}
},
{
listItems: observable,
addItem: action.bound
}
);
};
export const store = createListStore();
Working example
I would like your take on a specific implementation. I have a react app (no redux), the app has a shopping cart. The shopping cart is defined in the state in the App component and it is passed and used further down the tree in several components. E.g. I have a component called ShoppingCart, it displays the shopping cart, plus it has actions to add/remove/clear the cart.
My problem is updating the shopping cart state after performing an action on the shopping cart. E.g. when I call a function to clear the shopping cart, the state should be updated in the App component thus updating my component which is further down the tree. How would one implement these action functions (without redux)?
Code:
const App = () => {
const [cart, setCart] = useState({ lines: [], total: 0 });
return <ShoppingCart cart={cart} />;
}
const ShoppingCart = ({ cart }) => {
const onAddOne = l => {
// not sure how to update cart and update state
}
const onRemoveOne = l => {
// not sure how to update cart and update state
}
return (
<table>
{
cart.lines.map(l => <tr><td>{l.name}</td><td><button onClick={() => onAddOne(l)}>+</button><button onClick={() => onRemoveOne(l)}>-</button></td></tr>)
}
</table>
);
}
Thanks in advance for any tip.
Here you can use the useContext hook.
The idea is similar to redux.
So, what you can do is, first create a StateProvider, like in the example
import React, { createContext, useReducer, useContext } from "react";
export const StateContext = createContext();
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
Similarly, create a Reducer for that, you can add more reducers, the example shown is to ADD ITEMS IN BASKET and REMOVE ITEMs FROM BASKET
export const initialState = {
basket: [],
user: null,
};
export const getBasketTotal = (basket) =>
basket?.reduce((amount, item) => item.price + amount, 0);
function reducer(state, action) {
switch (action.type) {
case "ADD_TO_BASKET":
return { ...state, basket: [...state.basket, action.item] };
case "REMOVE_ITEM":
let newBasket = [...state.basket];
const index = state.basket.findIndex(
(basketItem) => basketItem.id === action.id
);
if (index >= 0) {
newBasket.splice(index, 1);
} else {
console.warn("Cant do this");
}
return { ...state, basket: newBasket };
default:
return state;
}
}
export default reducer;
Go to your index.js file and wrap your file like this
<StateProvider initialState={initialState} reducer={reducer}>
<App />
</StateProvider>
And voila, while adding items to the basket use following code
const addtobasket = () => {
dispatch({
type: "ADD_TO_BASKET",
item: {
id: id,
title: title,
price: price,
rating: rating,
color: color,
},
});
};
I found a solution, however, I am not sure it is the correct way to do things:
const App = () => {
const onUpdateCart = (cart) => {
setCart({ ...cart });
}
const [cart, setCart] = useState({ lines: [], total: 0, onUpdateCart });
return <ShoppingCart cart={cart} />;
}
const ShoppingCart = ({ cart }) => {
const onRemoveLine = l => {
cart.lines = cart.lines.filter(l2 => l2 !== l);
cart.onUpdateCart(cart);
}
const onAddOne = l => {
l.amount++;
cart.onUpdateCart(cart);
}
const onRemoveOne = l => {
l.amount--;
cart.onUpdateCart(cart);
}
return (
<table>
{
cart.lines.map(l => (
<tr>
<td>{l.name}</td>
<td>
<button onClick={() => onAddOne(l)}>+</button>
<button onClick={() => onRemoveOne(l)}>-</button>
<button onClick={() => onRemoveLine(l)}>x</button>
</td>
</tr>)
)
}
</table>
);
};
The straight forward way to implement this is to pass down props to the child component that when called update the state.
Notice how all state business logic is in a central place .e.g in App component. This allows ShoppingCart to be a much simpler.
const App = () => {
const [cart, setCart] = useState({ lines: [], total: 0 });
const updateLineAmount = (lineIdx, amount) => {
// update the amount on a specific line index
setCart((state) => ({
...state,
lines: state.lines.map((line, idx) => {
if (idx !== lineIdx) {
return line;
}
return {
...line,
amount: line.amount + amount,
};
}),
}));
};
const onAddOne = (lineIdx) => {
updateLineAmount(lineIdx, 1);
};
const onRemoveOne = (lineIdx) => {
updateLineAmount(lineIdx, -1);
};
return (
<ShoppingCart cart={cart} onAddOne={onAddOne} onRemoveOne={onRemoveOne} />
);
};
const ShoppingCart = ({ cart, onAddOne, onRemoveOne }) => {
return (
<table>
{cart.lines.map((line, idx) => (
<tr key={idx}>
<td>{line.name}</td>
<td>
<button onClick={() => onAddOne(idx)}>+</button>
<button onClick={() => onRemoveOne(idx)}>-</button>
</td>
</tr>
))}
</table>
);
};
I have a list of items (JSON objects returned from API) in a redux store. This data is normalized currently, so it's just an array. It'll roughly have 10-30 objects and each object will have about 10 properties.
Currently we have a top level container (uses react-redux connect) that reads this list from the store, maps over the array and renders a component called ListItem, which basically needs 3-4 fields from the object to render the UI.
We don't have any performance issue with this now. But I wonder if it makes sense to have a redux container component for each list item? I think this will require data to be normalized and we'd need the unique id of each object to be passed to this container which can then read the object from redux store?
This question arises from the Redux docs' Style Guide - https://redux.js.org/style-guide/style-guide#connect-more-components-to-read-data-from-the-store
Just trying to understand which is the recommended way to use react-redux in this scenario.
Thanks!
I wonder if it makes sense to have a redux container component for each list item?
Besides possibly better performance there is also better code reuse. If the logic of what an item is is defined in the list then how can you reuse the list to render other items?
Below is an example where item is a combination of data and edit so props for item will be recreated for all items if you'd create the props in List instead of Item.
List can also not be used as a general list that passes id to Item component.
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { useMemo } = React;
const { createSelector } = Reselect;
const { produce } = immer;
const initialState = {
people: {
data: {
1: { id: 1, name: 'Jon' },
2: { id: 2, name: 'Marie' },
},
edit: {},
},
places: {
data: {
1: { id: 1, name: 'Rome' },
2: { id: 2, name: 'Paris' },
},
edit: {},
},
};
//action types
const SET_EDIT = 'SET_EDIT';
const CANCEL_EDIT = 'CANCEL_EDIT';
const SAVE = 'SAVE';
const CHANGE_TEXT = 'CHANGE_TEXT';
//action creators
const setEdit = (dataType, id) => ({
type: SET_EDIT,
payload: { dataType, id },
});
const cancelEdit = (dataType, id) => ({
type: CANCEL_EDIT,
payload: { dataType, id },
});
const save = (dataType, item) => ({
type: SAVE,
payload: { dataType, item },
});
const changeText = (dataType, id, field, value) => ({
type: CHANGE_TEXT,
payload: { dataType, id, field, value },
});
const reducer = (state, { type, payload }) => {
if (type === SET_EDIT) {
const { dataType, id } = payload;
return produce(state, (draft) => {
draft[dataType].edit[id] = draft[dataType].data[id];
});
}
if (type === CANCEL_EDIT) {
const { dataType, id } = payload;
return produce(state, (draft) => {
delete draft[dataType].edit[id];
});
}
if (type === CHANGE_TEXT) {
const { dataType, id, field, value } = payload;
return produce(state, (draft) => {
draft[dataType].edit[id][field] = value;
});
}
if (type === SAVE) {
const { dataType, item } = payload;
return produce(state, (draft) => {
const newItem = { ...item };
delete newItem.edit;
draft[dataType].data[item.id] = newItem;
delete draft[dataType].edit[item.id];
});
}
return state;
};
//selectors
const createSelectData = (dataType) => (state) =>
state[dataType];
const createSelectDataList = (dataType) =>
createSelector([createSelectData(dataType)], (result) =>
Object.values(result.data)
);
const createSelectDataById = (dataType, itemId) =>
createSelector(
[createSelectData(dataType)],
(dataResult) => dataResult.data[itemId]
);
const createSelectEditById = (dataType, itemId) =>
createSelector(
[createSelectData(dataType)],
(dataResult) => (dataResult.edit || {})[itemId]
);
const createSelectItemById = (dataType, itemId) =>
createSelector(
[
createSelectDataById(dataType, itemId),
createSelectEditById(dataType, itemId),
],
(item, edit) => ({
...item,
...edit,
edit: Boolean(edit),
})
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
const Item = ({ item, dataType }) => {
const dispatch = useDispatch();
return (
<li>
{item.edit ? (
<React.Fragment>
<input
type="text"
value={item.name}
onChange={(e) =>
dispatch(
changeText(
dataType,
item.id,
'name',
e.target.value
)
)
}
/>
<button
onClick={() =>
dispatch(cancelEdit(dataType, item.id))
}
>
cancel
</button>
<button
onClick={() => dispatch(save(dataType, item))}
>
save
</button>
</React.Fragment>
) : (
<React.Fragment>
{item.name}
<button
onClick={() =>
dispatch(setEdit(dataType, item.id))
}
>
edit
</button>
</React.Fragment>
)}
</li>
);
};
const createItem = (dataType) =>
React.memo(function ItemContainer({ id }) {
const selectItem = useMemo(
() => createSelectItemById(dataType, id),
[id]
);
const item = useSelector(selectItem);
return <Item item={item} dataType={dataType} />;
});
const Person = createItem('people');
const Location = createItem('places');
const List = React.memo(function List({ items, Item }) {
return (
<ul>
{items.map(({ id }) => (
<Item key={id} id={id} />
))}
</ul>
);
});
const App = () => {
const [selectPeople, selectPlaces] = useMemo(
() => [
createSelectDataList('people'),
createSelectDataList('places'),
],
[]
);
const people = useSelector(selectPeople);
const places = useSelector(selectPlaces);
return (
<div>
<List items={people} Item={Person} />
<List items={places} Item={Location} />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<script src="https://unpkg.com/immer#7.0.5/dist/immer.umd.production.min.js"></script>
<div id="root"></div>
If your application has repeating logic you may want to think of splitting the component up in container and presentation (container also called connected component in redux). You can re use the container but change the presentation.