I use react table v7 with editable as defaultColumn, I have a log that returns the index row and the column index.
Is it possible to add a class to the cell if the index row and index column crosses.
my log that returns the row and column index :
const log = {rowIndex:1, columnIndex:10}
The expected result :
editable component :
import React, { useEffect, useState } from 'react';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
updateMyData,
}: {
value: any;
row: any;
column: any;
updateMyData: any;
}) => {
const [value, setValue] = useState(initialValue);
const [textCount, setTextCount] = useState<number>(0);
const tooltipName = <Tooltip id="tooltipName">{value}</Tooltip>;
const onChange = (e: any) => {
setValue(e.target.value);
};
const onBlur = () => {
updateMyData(index, id, value);
};
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
useEffect(() => {
if (value) {
setTextCount(value.length);
}
}, [value]);
return (
<>
{textCount > 15 ? (
<OverlayTrigger placement="top" overlay={tooltipName}>
<input value={value} onChange={onChange} onBlur={onBlur} />
</OverlayTrigger>
) : (
<>
<input value={value} onChange={onChange} onBlur={onBlur} />
</>
)}
</>
);
};
export default EditableCell;
grid :
import React from 'react';
import { Table } from 'react-bootstrap';
import { useSortBy, useTable } from 'react-table';
import GridRow from './grid-row';
import GridTfoot from './grid-tfoot';
import GridThead from './grid-thead';
import EditableCell from './grid-editable';
const SimpleGrid = (props: {
rows: any[];
columns: any;
extraCssClassName: string;
updateMyData?: any;
defaultSort?: any[];
editable?: boolean;
}) => {
const defaultColumn = {
Cell: EditableCell,
};
const {
getTableProps,
getTableBodyProps,
headerGroups,
footerGroups,
rows,
prepareRow,
} = useTable(
{
columns: props.columns,
data: props.rows,
initialState: {
sortBy: props.defaultSort,
},
defaultColumn: props.editable ? defaultColumn : undefined,
updateMyData: props.updateMyData,
},
useSortBy
);
return (
<Table
className={`table-nostriped grid-simple mb-0 ${props.extraCssClassName}`}
responsive
size="sm"
{...getTableProps()}
>
<GridThead headers={headerGroups} />
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row);
return <GridRow row={row} key={i} />;
})}
</tbody>
<GridTfoot footerGroups={footerGroups} />
</Table>
);
};
SimpleGrid.defaultProps = { extraCssClassName: '', defaultSort: [] };
export default SimpleGrid;
updateMyData :
const updateMyData = (rowIndex: any, columnId: any, value: any) => {
setData((old: any) =>
old.map((row: any, index: any) => {
if (index === rowIndex) {
return {
...old[rowIndex],
[columnId]: value,
};
}
return row;
})
);
};
Related
I am trying to integrate next-redux-wrapper to Next.js RTK project. When invoking async action from getServerSideProps, I am getting state mismatch error (see the image below).
When I dispatch action from client side (increment/decrement), everything works well. I think issue is related to HYDRATION but so far all my efforts have failed.
I tried mapping redux state to props, storing props in component state, added if statements to check values but nothing seem to work. I've been stuck on this for 2 weeks. I'm not sure what else to try next.
"next": "12.3.1",
"next-redux-wrapper": "^8.0.0",
"react":
"18.2.0",
"react-redux": "^8.0.4"
store/store.js
import { configureStore, combineReducers } from "#reduxjs/toolkit";
import counterReducer from "./slices/counterSlice";
import { createWrapper, HYDRATE } from "next-redux-wrapper";
const combinedReducer = combineReducers({
counter: counterReducer,
});
const reducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
return nextState;
} else {
return combinedReducer(state, action);
}
};
export const makeStore = () =>
configureStore({
reducer,
});
export const wrapper = createWrapper(makeStore, { debug: true });
store/slices/counterSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const initialState = {
value: 0,
data: { quote: "" },
pending: false,
error: false,
};
export const getKanyeQuote = createAsyncThunk(
"counter/kanyeQuote",
async () => {
const respons = await axios.get("https://api.kanye.rest/");
return respons.data;
}
);
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(getKanyeQuote.pending, (state) => {
state.pending = true;
})
.addCase(getKanyeQuote.fulfilled, (state, { payload }) => {
state.pending = false;
state.data = payload;
})
.addCase(getKanyeQuote.rejected, (state) => {
state.pending = false;
state.error = true;
});
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
pages/index.js
import React, { useState } from "react";
import { useSelector, useDispatch, connect } from "react-redux";
import {
decrement,
increment,
getKanyeQuote,
} from "../store/slices/counterSlice";
import { wrapper } from "../store/store";
function Home({ data }) {
const count = useSelector((state) => state.counter.value);
// const { data, pending, error } = useSelector((state) => state.counter);
const dispatch = useDispatch();
const [quote, setQuote] = useState(data.quote);
return (
<div style={{ display: "flex", flexDirection: "column" }}>
{/* <span>{pending && <p>Loading...</p>}</span>
<span>{error && <p>Oops, something went wrong</p>}</span> */}
<div>{quote}</div>
<span>Count: {count}</span>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
);
}
export const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ({ req, res, ...etc }) => {
console.log(
"2. Page.getServerSideProps uses the store to dispatch things"
);
await store.dispatch(getKanyeQuote());
}
);
function mapStateToProps(state) {
return {
data: state.counter.data,
};
}
export default connect(mapStateToProps)(Home);
Errors in console
This might stem from a known issue where next-redux-wrapper 8 hydrates too late. Please try downgrading to version 7 for now and see if that resolves the problem.
//PRODUCT_DETAILS
import React, { Fragment, useEffect } from "react";
import Carousel from "react-material-ui-carousel";
import { useSelector, useDispatch } from "react-redux";
import { getProductDetails } from "../../actions/productAction";
import { useParams } from "react-router-dom";
const ProductDetails = () => {
const { id } = useParams();
const dispatch = useDispatch();
const { product } = useSelector((state) => state.productDetails);
useEffect(() => {
dispatch(getProductDetails(id));
}, [dispatch, id]);
return (
<Fragment>
<div className="ProductDetails">
<div>
<Carousel>
{product &&
product.images &&
product.images.map((item, i) => (
<img
className="CarouselImage"
key={item.url}
src={item.url}
alt={`${i}Slide`}
/>
))}
</Carousel>
</div>
</div>
</Fragment>
);
};
export default ProductDetails;
//PRODUCT_REDUCER
import {
CLEAR_ERRORS,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
} from "../constants/productConstants";
export const productDetailsReducer = (state = { product: {} }, action) => {
switch (action.type) {
case PRODUCT_DETAILS_REQUEST:
return {
loading: true,
...state,
};
case PRODUCT_DETAILS_SUCCESS:
return {
loading: false,
product: action.payload.product,
};
case PRODUCT_DETAILS_FAIL:
return {
loading: false,
error: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
//PRODUCT_ACTION
import axios from "axios";
import {
CLEAR_ERRORS,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
} from "../constants/productConstants";
export const getProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST });
const { data } = await axios.get(`api/v1/product${id}`);
dispatch({
type: PRODUCT_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
Payload: error.response.data.message,
});
}
};
// clearing errors
export const clearErrors = () => async (dispatch) => {
dispatch({ type: CLEAR_ERRORS });
};
//PRODUCT_CONSTANTS
How to create a Datagrid without a List in React-Admin?
In React-admin to be able to use Datagrid to show the values you need to nest it inside a List like that:
<List {...props}>
<Datagrid>
<Textfield source"name" />
<Datagrid />
<List/>
But I need to use the dDatagrid outside of the List. The values of the Datagrid must to be shown in a screen of the type inside a Tab.
Here how I solved that problem using React-Admin 2.9
First I used The import { Query } from 'react-admin'; to fetch the data from the endpoint data/product and after that It converted the IDs to the key of it's own data.
To the commponent CustomDatagrid was passed two arrays the will be used to show the values. The first is a array of IDs and the second array is the array that contains the data and uses IDs as its key to be able to access the values.
ProductShow.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
TextField,
Query,
showNotification,
// ReferenceField,
DateField,
} from 'react-admin';
import CustomDatagrid from './CustomDatagrid';
const convertKeyToIndice = (datas) => {
const { data } = datas;
let ids = [];
const dataKey = [];
if (data) {
ids = data.map((elemento) => {
dataKey[elemento.id] = elemento;
return elemento.id;
});
return ({ ids, dataKey });
}
return ({});
};
const ProductShow = (props) => {
const { record } = props;
const { productId, dateValue } = record;
const [newPage, setNewPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(50);
const filter = {
pagination: { page: newPage + 1, perPage: rowsPerPage },
sort: { field: 'id', order: 'ASC' },
filter: { productId, dateValue: dateValue },
};
return (
<Query type="GET_LIST" resource="data/product" payload={filter}>
{(datas) => {
if (datas.loading && !datas.error) { return <div>Wait...</div>; }
if (datas.error) {
props.showNotification('Not Found');
return <div />;
}
const dataGridValues = {
...convertKeyToIndice(datas),
total: datas.total,
rowsPerPage,
setRowsPerPage,
newPage,
setNewPage,
};
return (
<>
<CustomDatagrid dataGridValues={dataGridValues}>
{/* <ReferenceField
sortBy="product.id"
source="productId"
reference="product"
linkType={false}
allowEmpty
{...props}
label="product"
>
<TextField source="name" />
</ReferenceField> */}
<DateField label="Date" source="valueDate" showTime />
<TextField label="Value1" source="value1" />
<TextField label="Value2" source="value2" />
<TextField label="Value3" source="value3" />
<TextField label="Value4" source="value4" />
<TextField label="Value5" source="value5" />
</CustomDatagrid>
</>
);
}}
</Query>
);
};
ProductShow.propTypes = {
productId: PropTypes.string.isRequired,
dateValue: PropTypes.string.isRequired,
showNotification: PropTypes.func.isRequired,
record: PropTypes.oneOfType([PropTypes.object]).isRequired,
};
export default connect(null, { showNotification })(ProductShow);
In the TableHead it shows the label of each column following the order that you wrap the Components in the CustomDatagrid inside the parent ProductShow component.
To Show The Components Like the List of React-Admin would show, is used the
{React.cloneElement(child,
{ record: dataKey[id] },
(child.props.children ? child.props.children : null))}
CustomDatagrid.js
import React from 'react';
import {
Table,
TableRow,
TableHead,
TableCell,
TableBody,
TablePagination,
} from '#material-ui/core';
import PropTypes from 'prop-types';
const CustomDatagrid = (propsDataGrid) => {
const { children, dataGridValues } = propsDataGrid;
const {
dataKey,
ids,
total,
rowsPerPage,
setRowsPerPage,
setNewPage,
newPage,
} = dataGridValues;
const handleChangePage = (event, page) => {
setNewPage(page);
};
const handleChangeRowsPerPage = (event) => {
const vlrDecimal = parseInt(event.target.value, 10);
setRowsPerPage(vlrDecimal);
setNewPage(0);
};
return (
<>
<Table style={{ width: '100%' }}>
<TableHead>
<TableRow>
{children && children.map(({ props }) => (
<TableCell>
{props && props.label ? props.label : props.source}
</TableCell>
))
}
</TableRow>
</TableHead>
<TableBody>
{ids && ids.map(id => (
<TableRow>
{React.Children.map(children,
child => (
<TableCell>
{React.cloneElement(child,
{ record: dataKey[id] },
(child.props.children ? child.props.children : null))}
</TableCell>
))}
</TableRow>
))
}
</TableBody>
</Table>
<TablePagination
rowsPerPageOptions={[50, 100, 250]}
component="div"
count={total}
rowsPerPage={rowsPerPage}
page={newPage}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
labelRowsPerPage="Results per Page:"
/>
</>
);
};
CustomDatagrid.propTypes = {
source: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
};
export default CustomDatagrid;
I'm new to Redux. It's really confusing to understand basic syntax. None of the bugs are found so It's hard to figure out what's wrong with my code.
It worked well last week, I don't remember what I have changed.
//child component
import React, { Component } from 'react';
import { SingleDatePicker } from 'react-dates';
import moment from 'moment';
class InputForms extends Component {
state = {
inputs: ['input-0'],
title: '',
tag: '',
createdAt: moment(),
imageLinks: [''],
calendarFocused: false,
error: '',
}
appendInput(e) {
const newInput = `input-${this.state.inputs.length}`;
this.setState({ inputs: this.state.inputs.concat([newInput]) });
}
onTitleChange = (e) => {
const title = e.target.value;
this.setState(() => ({ title }));
};
onTagChange = (e) => {
const tag = e.target.value;
this.setState(() => ({ tag }));
};
onImageLinkChange = (e) => {
const imageLinks = e.target.value;
this.setState(() => ({ imageLinks: this.state.imageLinks.concat([imageLinks]) }));
};
onDateChange = (createdAt) => {
if (createdAt) {
this.setState(() => ({ createdAt }));
}
};
onFocusChange = ({ focused }) => {
this.setState(() => ({ calendarFocused: focused }));
};
onSubmit = (e) => {
e.preventDefault();
if (!this.state.title || !this.state.imageLinks) {
this.setState(() => ({ error: '제목과 이미지링크를 입력해주세요' }));
} else {
this.setState(() => ({ error: '' }));
this.props.onSubmit({
title: this.state.title,
tag: this.state.tag,
createdAt: this.state.createdAt.valueOf(),
imageLinks: this.state.imageLinks,
});
}
}
render() {
return (
<div>
<form onSubmit={this.onSubmit}>
<input
type="text"
placeholder="제목을 입력하세요"
required
value={this.state.title}
onChange={this.onTitleChange}
/>
<input
type="text"
placeholder="태그를 입력하세요"
value={this.state.tag}
onChange={this.onTagChange}
/>
<SingleDatePicker
date={this.state.createdAt}
onDateChange={this.onDateChange}
focused={this.state.calendarFocused}
onFocusChange={this.onFocusChange}
numberOfMonths={1}
isOutsideRange={() => false}
/>
{this.state.inputs.map((input, key) => {
return <input
key={input}
type="text"
required
value={this.state.imageLinks}
onChange={this.onImageLinkChange}
placeholder={`이미지링크 ${key + 1}`}
/>
})}
<button>저장</button>
</form>
</div>
)
}
}
export default InputForms;
//parent component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import configureStore from '../store/configureStore';
import InputForms from './InputForms';
import { addPost } from '../actions/posts';
const store = configureStore();
class CreatePost extends Component {
onSubmit = (post) => {
this.props.addPost(post);
this.props.history.push('/');
};
render(){
return(
<div>
<InputForms onSubmit={this.onSubmit}/>
</div>
)
}
}
const mapDispatchToProps = (dispatch, props) => ({
addPost: (post) => dispatch(addPost(post))
});
export default connect(undefined, mapDispatchToProps)(CreatePost);
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import registerServiceWorker from './registerServiceWorker';
import AppRouter from './routers/AppRouter';
import 'normalize.css/normalize.css';
import './style/style.css';
import 'react-dates/lib/css/_datepicker.css';
import 'react-dates/initialize';
const store = configureStore();
const jsx = (
<Provider store={store}>
<AppRouter />
</Provider>
);
ReactDOM.render(jsx, document.getElementById('root'));
registerServiceWorker();
//action
import database from '../firebase/firebase';
//Add Posts
export const addPost = (post) => ({
type: 'ADD_POST',
post
});
//reducer
const postReducerDefaultState = [];
export default (state = postReducerDefaultState, action) => {
switch (action.type) {
case 'ADD_POST':
return [
...state,
action.post
];
default:
return state;
}
};
In your reducer, you return as below
return [ ...state, action.post];
Reducer doesnt return array, but instead returning objects. Secondly, action.post is a value, you need to assign this to key, something like below:
return { ...state, post: action.post };
I am using redux to store and fetch my data from Firebase. The data is stored successfully, however, the view MyActivities is not updated with the new data. Am I doing something wrong here ?
I need to rerender the whole application for the views to be updated with the new data stored in Firebase.
I didn't add all the code used in this example.
ActivitiesAction.js
import firebase from 'firebase';
import { Navigation } from 'react-native-navigation';
import {
ACTIVITIES_FETCH_SUCCESS,
ACTIVITIES_SEND_SUCCESS,
SUBACTIVITY_SEND_SUCCESS
} from './types';
export const activitiesFetch = () => {
return (dispatch) => {
firebase.database().ref('users_data/s80GnOQu22W4XLLYbuKUBC2BzkY2/').once('value')
.then((snapshot) => {
dispatch({
type: ACTIVITIES_FETCH_SUCCESS,
payload: snapshot.val()
});
});
};
};
export const activitiesSend = (activityDescription,
activityDate, category, notification, alarm) => {
const ref = firebase.database()
.ref('users_data/s80GnOQu22W4XLLYbuKUBC2BzkY2/activities/cheduled_activities');
const activity = ref.push();
const ref2 = firebase.database()
.ref('users_data/s80GnOQu22W4XLLYbuKUBC2BzkY2/activities/all_activities');
const activityList = ref2.push();
return (dispatch) => {
activity.set({
activityDescription,
activityDate,
category,
// time: this.state.time,
notification,
alarm
}).then(activityCreated => {
dispatch({ type: ACTIVITIES_SEND_SUCCESS, payload: activityCreated });
activityList.set({
activityDescription,
category
}).then(listActivity => {
dispatch({ type: SUBACTIVITY_SEND_SUCCESS, payload: listActivity });
});
});
};
};
ActivitiesReducer.js
import {
ACTIVITIES_FETCH_SUCCESS,
ACTIVITIES_SEND_SUCCESS,
SUBACTIVITY_SEND_SUCCESS
} from '../actions/types';
const INITIAL_STATE = { activitiesData: '', activityCreated: null, listActivity: null };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case ACTIVITIES_FETCH_SUCCESS:
return { ...state, activitiesData: action.payload };
case ACTIVITIES_SEND_SUCCESS:
return { ...state, activityCreated: action.payload };
case SUBACTIVITY_SEND_SUCCESS:
return { ...state, listActivity: action.payload };
default:
return state;
}
};
MyActivities.js
import React, { Component } from 'react';
import { Container, Content, View } from 'native-base';
import { connect } from 'react-redux';
import _ from 'lodash';
import { activitiesFetch } from '../actions/ActivitiesAction';
import Activity from './Activity';
class MyActivities extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.activitiesFetch();
}
componentWillReceiveProps(nextProps) {
this.setState({ activitiesData: nextProps });
console.log('next props:', nextProps);
}
componentWillUpdate(nextProps, nextState) {
console.log('next props:', nextProps);
}
renderActivities() {
// this.setState({ data: this.props.activitiesArray });
console.log('acti():', this.props.activitiesData);
const activitiesArray = _.values(this.props.activitiesData);
console.log('acti[]:', activitiesArray);
const list = _.values(activitiesArray[0]) || [];
const act = list.map((activities) => (activities));
console.log('acti[]:', act);
const activities = _.values(act[1]);
return (
activities.map((singleActivity, i) => (
<Activity
key={i}
Title={singleActivity.activityDescription}
Author={activitiesArray[1].full_name}
Time={singleActivity.time}
PeopleStats={'0'}
/>
))
);
}
render() {
return (
<Container>
{/* <HeaderActivities /> */}
<Content style={styles.Content}>
{/* <ScrollView style={{ flex: 1 }}> */}
<View style={styles.styleView}>
{this.renderActivities()}
</View>
{/* </ScrollView> */}
</Content>
</Container>
);
}
}
const mapStateToProps = state => {
return {
// activitiesArray: _.values(state.activitiesData)
activitiesData: state.activitiesData
};
};
const styles = {
Content: {
backgroundColor: '#F0F5F7',
},
styleView: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
alignItems: 'center',
// alignItems: 'flex-start',
// alignContent: 'flex-start'
},
ButtonActive: {
borderBottomColor: '#FF8600',
borderBottomWidth: 3,
paddingBottom: 3,
borderRadius: 0
}
};
export default connect(mapStateToProps, { activitiesFetch })(MyActivities);
Use the redux and react dev tools to check
did the expected action dispatch?
does the state change match your expectations?
do components have the props you expect them to have?